blob: d0c52d774d8b9ad973acb70d6b933a6bdfc655bd [file] [log] [blame]
// Copyright 2010 Victor Iacoban
//
// 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 org.zmlx.hg4idea.util;
import com.intellij.dvcs.DvcsUtil;
import com.intellij.openapi.application.ApplicationManager;
import com.intellij.openapi.components.ServiceManager;
import com.intellij.openapi.diagnostic.Logger;
import com.intellij.openapi.editor.Document;
import com.intellij.openapi.editor.Editor;
import com.intellij.openapi.fileEditor.FileDocumentManager;
import com.intellij.openapi.fileEditor.FileEditor;
import com.intellij.openapi.fileEditor.FileEditorManager;
import com.intellij.openapi.fileEditor.TextEditor;
import com.intellij.openapi.project.Project;
import com.intellij.openapi.ui.Messages;
import com.intellij.openapi.util.Pair;
import com.intellij.openapi.util.ShutDownTracker;
import com.intellij.openapi.util.io.FileUtil;
import com.intellij.openapi.util.text.StringUtil;
import com.intellij.openapi.vcs.*;
import com.intellij.openapi.vcs.changes.*;
import com.intellij.openapi.vcs.history.FileHistoryPanelImpl;
import com.intellij.openapi.vcs.history.VcsFileRevisionEx;
import com.intellij.openapi.vcs.vfs.VcsVirtualFile;
import com.intellij.openapi.vfs.CharsetToolkit;
import com.intellij.openapi.vfs.LocalFileSystem;
import com.intellij.openapi.vfs.VirtualFile;
import com.intellij.openapi.wm.StatusBar;
import com.intellij.openapi.wm.WindowManager;
import com.intellij.openapi.wm.impl.status.StatusBarUtil;
import com.intellij.ui.GuiUtils;
import com.intellij.util.containers.ContainerUtil;
import com.intellij.vcsUtil.VcsUtil;
import org.jetbrains.annotations.NotNull;
import org.jetbrains.annotations.Nullable;
import org.zmlx.hg4idea.*;
import org.zmlx.hg4idea.command.HgRemoveCommand;
import org.zmlx.hg4idea.command.HgStatusCommand;
import org.zmlx.hg4idea.command.HgWorkingCopyRevisionsCommand;
import org.zmlx.hg4idea.execution.HgCommandResult;
import org.zmlx.hg4idea.execution.ShellCommand;
import org.zmlx.hg4idea.execution.ShellCommandException;
import org.zmlx.hg4idea.provider.HgChangeProvider;
import org.zmlx.hg4idea.repo.HgRepository;
import org.zmlx.hg4idea.repo.HgRepositoryManager;
import java.awt.*;
import java.io.*;
import java.lang.reflect.InvocationTargetException;
import java.util.*;
import java.util.List;
import java.util.regex.Matcher;
import java.util.regex.Pattern;
/**
* HgUtil is a collection of static utility methods for Mercurial.
*/
public abstract class HgUtil {
public static final Pattern URL_WITH_PASSWORD = Pattern.compile("(?:.+)://(?:.+)(:.+)@(?:.+)"); //http(s)://username:password@url
public static final int MANY_FILES = 100;
private static final Logger LOG = Logger.getInstance(HgUtil.class);
public static final String DOT_HG = ".hg";
public static final String TIP_REFERENCE = "tip";
public static File copyResourceToTempFile(String basename, String extension) throws IOException {
final InputStream in = HgUtil.class.getClassLoader().getResourceAsStream("python/" + basename + extension);
final File tempFile = FileUtil.createTempFile(basename, extension);
final byte[] buffer = new byte[4096];
OutputStream out = null;
try {
out = new FileOutputStream(tempFile, false);
int bytesRead;
while ((bytesRead = in.read(buffer)) != -1)
out.write(buffer, 0, bytesRead);
} finally {
try {
out.close();
}
catch (IOException e) {
// ignore
}
}
try {
in.close();
}
catch (IOException e) {
// ignore
}
tempFile.deleteOnExit();
return tempFile;
}
public static void markDirectoryDirty(final Project project, final VirtualFile file) throws InvocationTargetException, InterruptedException {
ApplicationManager.getApplication().runReadAction(new Runnable() {
public void run() {
VcsDirtyScopeManager.getInstance(project).dirDirtyRecursively(file);
}
});
runWriteActionAndWait(new Runnable() {
public void run() {
file.refresh(true, true);
}
});
}
public static void markFileDirty( final Project project, final VirtualFile file ) throws InvocationTargetException, InterruptedException {
ApplicationManager.getApplication().runReadAction(new Runnable() {
public void run() {
VcsDirtyScopeManager.getInstance(project).fileDirty(file);
}
} );
runWriteActionAndWait(new Runnable() {
public void run() {
file.refresh(true, false);
}
});
}
/**
* Runs the given task as a write action in the event dispatching thread and waits for its completion.
*/
public static void runWriteActionAndWait(@NotNull final Runnable runnable) throws InvocationTargetException, InterruptedException {
GuiUtils.runOrInvokeAndWait(new Runnable() {
public void run() {
ApplicationManager.getApplication().runWriteAction(runnable);
}
});
}
/**
* Schedules the given task to be run as a write action in the event dispatching thread.
*/
public static void runWriteActionLater(@NotNull final Runnable runnable) {
ApplicationManager.getApplication().invokeLater(new Runnable() {
public void run() {
ApplicationManager.getApplication().runWriteAction(runnable);
}
});
}
/**
* Returns a temporary python file that will be deleted on exit.
*
* Also all compiled version of the python file will be deleted.
*
* @param base The basename of the file to copy
* @return The temporary copy the specified python file, with all the necessary hooks installed
* to make sure it is completely removed at shutdown
*/
@Nullable
public static File getTemporaryPythonFile(String base) {
try {
final File file = copyResourceToTempFile(base, ".py");
final String fileName = file.getName();
ShutDownTracker.getInstance().registerShutdownTask(new Runnable() {
public void run() {
File[] files = file.getParentFile().listFiles(new FilenameFilter() {
public boolean accept(File dir, String name) {
return name.startsWith(fileName);
}
});
if (files != null) {
for (File file1 : files) {
file1.delete();
}
}
}
});
return file;
} catch (IOException e) {
return null;
}
}
/**
* Calls 'hg remove' to remove given files from the VCS.
* @param project
* @param files files to be removed from the VCS.
*/
public static void removeFilesFromVcs(Project project, List<FilePath> files) {
final HgRemoveCommand command = new HgRemoveCommand(project);
for (FilePath filePath : files) {
final VirtualFile vcsRoot = VcsUtil.getVcsRootFor(project, filePath);
if (vcsRoot == null) {
continue;
}
command.execute(new HgFile(vcsRoot, filePath));
}
}
/**
* Finds the nearest parent directory which is an hg root.
* @param dir Directory which parent will be checked.
* @return Directory which is the nearest hg root being a parent of this directory,
* or <code>null</code> if this directory is not under hg.
* @see com.intellij.openapi.vcs.AbstractVcs#isVersionedDirectory(com.intellij.openapi.vfs.VirtualFile)
*/
@Nullable
public static VirtualFile getNearestHgRoot(VirtualFile dir) {
VirtualFile currentDir = dir;
while (currentDir != null) {
if (isHgRoot(currentDir)) {
return currentDir;
}
currentDir = currentDir.getParent();
}
return null;
}
/**
* Checks if the given directory is an hg root.
*/
public static boolean isHgRoot(VirtualFile dir) {
return dir.findChild(DOT_HG) != null;
}
/**
* Gets the Mercurial root for the given file path or null if non exists:
* the root should not only be in directory mappings, but also the .hg repository folder should exist.
* @see #getHgRootOrThrow(com.intellij.openapi.project.Project, com.intellij.openapi.vcs.FilePath)
*/
@Nullable
public static VirtualFile getHgRootOrNull(Project project, FilePath filePath) {
if (project == null) {
return getNearestHgRoot(VcsUtil.getVirtualFile(filePath.getPath()));
}
return getNearestHgRoot(VcsUtil.getVcsRootFor(project, filePath));
}
/**
* Get hg roots for paths
*
* @param filePaths the context paths
* @return a set of hg roots
*/
@NotNull
public static Set<VirtualFile> hgRoots(@NotNull Project project, @NotNull Collection<FilePath> filePaths) {
HashSet<VirtualFile> roots = new HashSet<VirtualFile>();
for (FilePath path : filePaths) {
ContainerUtil.addIfNotNull(roots, getHgRootOrNull(project, path));
}
return roots;
}
/**
* Gets the Mercurial root for the given file path or null if non exists:
* the root should not only be in directory mappings, but also the .hg repository folder should exist.
* @see #getHgRootOrThrow(com.intellij.openapi.project.Project, com.intellij.openapi.vcs.FilePath)
* @see #getHgRootOrNull(com.intellij.openapi.project.Project, com.intellij.openapi.vcs.FilePath)
*/
@Nullable
public static VirtualFile getHgRootOrNull(Project project, @NotNull VirtualFile file) {
return getHgRootOrNull(project, VcsUtil.getFilePath(file.getPath()));
}
/**
* Gets the Mercurial root for the given file path or throws a VcsException if non exists:
* the root should not only be in directory mappings, but also the .hg repository folder should exist.
* @see #getHgRootOrNull(com.intellij.openapi.project.Project, com.intellij.openapi.vcs.FilePath)
*/
@NotNull
public static VirtualFile getHgRootOrThrow(Project project, FilePath filePath) throws VcsException {
final VirtualFile vf = getHgRootOrNull(project, filePath);
if (vf == null) {
throw new VcsException(HgVcsMessages.message("hg4idea.exception.file.not.under.hg", filePath.getPresentableUrl()));
}
return vf;
}
@NotNull
public static VirtualFile getHgRootOrThrow(Project project, VirtualFile file) throws VcsException {
return getHgRootOrThrow(project, VcsUtil.getFilePath(file.getPath()));
}
/**
* Returns the currently selected file, based on which HgBranch components will identify the current repository root.
*/
@Nullable
public static VirtualFile getSelectedFile(@NotNull Project project) {
StatusBar statusBar = WindowManager.getInstance().getStatusBar(project);
final FileEditor fileEditor = StatusBarUtil.getCurrentFileEditor(project, statusBar);
VirtualFile result = null;
if (fileEditor != null) {
if (fileEditor instanceof TextEditor) {
Document document = ((TextEditor)fileEditor).getEditor().getDocument();
result = FileDocumentManager.getInstance().getFile(document);
}
}
if (result == null) {
final FileEditorManager manager = FileEditorManager.getInstance(project);
if (manager != null) {
Editor editor = manager.getSelectedTextEditor();
if (editor != null) {
result = FileDocumentManager.getInstance().getFile(editor.getDocument());
}
}
}
return result;
}
@Nullable
public static VirtualFile getRootForSelectedFile(@NotNull Project project) {
VirtualFile selectedFile = getSelectedFile(project);
if (selectedFile != null) {
return getHgRootOrNull(project, selectedFile);
}
return null;
}
/**
* Shows a message dialog to enter the name of new branch.
*
* @return name of new branch or {@code null} if user has cancelled the dialog.
*/
@Nullable
public static String getNewBranchNameFromUser(@NotNull HgRepository repository,
@NotNull String dialogTitle) {
return Messages.showInputDialog(repository.getProject(), "Enter the name of new branch:", dialogTitle, Messages.getQuestionIcon(), "",
HgReferenceValidator.newInstance(repository));
}
/**
* Checks is a merge operation is in progress on the given repository.
* Actually gets the number of parents of the current revision. If there are 2 parents, then a merge is going on. Otherwise there is
* only one parent.
* @param project project to work on.
* @param repository repository which is checked on merge.
* @return True if merge operation is in progress, false if there is no merge operation.
*/
public static boolean isMergeInProgress(@NotNull Project project, VirtualFile repository) {
return new HgWorkingCopyRevisionsCommand(project).parents(repository).size() > 1;
}
/**
* Groups the given files by their Mercurial repositories and returns the map of relative paths to files for each repository.
* @param hgFiles files to be grouped.
* @return key is repository, values is the non-empty list of relative paths to files, which belong to this repository.
*/
@NotNull
public static Map<VirtualFile, List<String>> getRelativePathsByRepository(Collection<HgFile> hgFiles) {
final Map<VirtualFile, List<String>> map = new HashMap<VirtualFile, List<String>>();
if (hgFiles == null) {
return map;
}
for(HgFile file : hgFiles) {
final VirtualFile repo = file.getRepo();
List<String> files = map.get(repo);
if (files == null) {
files = new ArrayList<String>();
map.put(repo, files);
}
files.add(file.getRelativePath());
}
return map;
}
@NotNull
public static HgFile getFileNameInTargetRevision(Project project, HgRevisionNumber vcsRevisionNumber, HgFile localHgFile) {
//get file name in target revision if it was moved/renamed
HgStatusCommand statCommand = new HgStatusCommand.Builder(false).copySource(true).baseRevision(vcsRevisionNumber).build(project);
Set<HgChange> changes = statCommand.execute(localHgFile.getRepo(), Collections.singletonList(localHgFile.toFilePath()));
for (HgChange change : changes) {
if (change.afterFile().equals(localHgFile)) {
return change.beforeFile();
}
}
return localHgFile;
}
@NotNull
public static FilePath getOriginalFileName(@NotNull FilePath filePath, ChangeListManager changeListManager) {
Change change = changeListManager.getChange(filePath);
if (change == null) {
return filePath;
}
FileStatus status = change.getFileStatus();
if (status == HgChangeProvider.COPIED ||
status == HgChangeProvider.RENAMED) {
ContentRevision beforeRevision = change.getBeforeRevision();
assert beforeRevision != null : "If a file's status is copied or renamed, there must be an previous version";
return beforeRevision.getFile();
}
else {
return filePath;
}
}
/**
* Returns all HG roots in the project.
*/
public static @NotNull List<VirtualFile> getHgRepositories(@NotNull Project project) {
final List<VirtualFile> repos = new LinkedList<VirtualFile>();
for (VcsRoot root : ProjectLevelVcsManager.getInstance(project).getAllVcsRoots()) {
if (HgVcs.VCS_NAME.equals(root.getVcs().getName())) {
repos.add(root.getPath());
}
}
return repos;
}
@NotNull
public static Map<VirtualFile, Collection<VirtualFile>> sortByHgRoots(@NotNull Project project, @NotNull Collection<VirtualFile> files) {
Map<VirtualFile, Collection<VirtualFile>> sorted = new HashMap<VirtualFile, Collection<VirtualFile>>();
HgRepositoryManager repositoryManager = getRepositoryManager(project);
for (VirtualFile file : files) {
HgRepository repo = repositoryManager.getRepositoryForFile(file);
if (repo == null) {
continue;
}
Collection<VirtualFile> filesForRoot = sorted.get(repo.getRoot());
if (filesForRoot == null) {
filesForRoot = new HashSet<VirtualFile>();
sorted.put(repo.getRoot(), filesForRoot);
}
filesForRoot.add(file);
}
return sorted;
}
@NotNull
public static Map<VirtualFile, Collection<FilePath>> groupFilePathsByHgRoots(@NotNull Project project,
@NotNull Collection<FilePath> files) {
Map<VirtualFile, Collection<FilePath>> sorted = new HashMap<VirtualFile, Collection<FilePath>>();
HgRepositoryManager repositoryManager = getRepositoryManager(project);
for (FilePath file : files) {
HgRepository repo = repositoryManager.getRepositoryForFile(file);
if (repo == null) {
continue;
}
Collection<FilePath> filesForRoot = sorted.get(repo.getRoot());
if (filesForRoot == null) {
filesForRoot = new HashSet<FilePath>();
sorted.put(repo.getRoot(), filesForRoot);
}
filesForRoot.add(file);
}
return sorted;
}
public static void executeOnPooledThreadIfNeeded(Runnable runnable) {
if (EventQueue.isDispatchThread()) {
ApplicationManager.getApplication().executeOnPooledThread(runnable);
} else {
runnable.run();
}
}
/**
* Convert {@link VcsVirtualFile} to the {@link LocalFileSystem local} Virtual File.
*
* TODO
* It is a workaround for the following problem: VcsVirtualFiles returned from the {@link FileHistoryPanelImpl} contain the current path
* of the file, not the path that was in certain revision. This has to be fixed by making {@link HgFileRevision} implement
* {@link VcsFileRevisionEx}.
*/
@Nullable
public static VirtualFile convertToLocalVirtualFile(@Nullable VirtualFile file) {
if (!(file instanceof VcsVirtualFile)) {
return file;
}
LocalFileSystem lfs = LocalFileSystem.getInstance();
VirtualFile resultFile = lfs.findFileByPath(file.getPath());
if (resultFile == null) {
resultFile = lfs.refreshAndFindFileByPath(file.getPath());
}
return resultFile;
}
@NotNull
public static List<Change> getDiff(@NotNull final Project project,
@NotNull final VirtualFile root,
@NotNull final FilePath path,
@Nullable final HgFileRevision rev1,
@Nullable final HgFileRevision rev2) {
HgStatusCommand statusCommand;
HgRevisionNumber revNumber1 = null;
if (rev1 != null) {
revNumber1 = rev1.getRevisionNumber();
//rev2==null means "compare with local version"
statusCommand = new HgStatusCommand.Builder(true).ignored(false).unknown(false).copySource(false).baseRevision(revNumber1)
.targetRevision(rev2 != null ? rev2.getRevisionNumber() : null).build(project);
}
else {
LOG.assertTrue(rev2 != null, "revision1 and revision2 can't both be null. Path: " + path); //rev1 and rev2 can't be null both//
//get initial changes//
statusCommand =
new HgStatusCommand.Builder(true).ignored(false).unknown(false).copySource(false).baseRevision(rev2.getRevisionNumber())
.build(project);
}
Collection<HgChange> hgChanges = statusCommand.execute(root, Collections.singleton(path));
List<Change> changes = new ArrayList<Change>();
//convert output changes to standart Change class
for (HgChange hgChange : hgChanges) {
FileStatus status = convertHgDiffStatus(hgChange.getStatus());
if (status != FileStatus.UNKNOWN) {
changes.add(createChange(project, root, hgChange.beforeFile().getRelativePath(), revNumber1,
hgChange.afterFile().getRelativePath(),
rev2 != null ? rev2.getRevisionNumber() : null, status));
}
}
return changes;
}
@NotNull
public static Change createChange(@NotNull final Project project, VirtualFile root,
@NotNull String fileBefore,
@Nullable HgRevisionNumber revisionBefore,
@NotNull String fileAfter,
@Nullable HgRevisionNumber revisionAfter,
@NotNull FileStatus aStatus) {
HgContentRevision beforeRevision = revisionBefore == null
? null
: new HgContentRevision(project, new HgFile(root, new File(root.getPath(), fileBefore)),
revisionBefore);
if (revisionAfter == null) {
ContentRevision currentRevision =
CurrentContentRevision.create(new HgFile(root, new File(root.getPath(), fileBefore)).toFilePath());
return new Change(beforeRevision, currentRevision, aStatus);
}
HgContentRevision afterRevision = new HgContentRevision(project,
new HgFile(root, new File(root.getPath(), fileAfter)),
revisionAfter);
return new Change(beforeRevision, afterRevision, aStatus);
}
@NotNull
public static FileStatus convertHgDiffStatus(@NotNull HgFileStatusEnum hgstatus) {
if (hgstatus.equals(HgFileStatusEnum.ADDED)) {
return FileStatus.ADDED;
}
else if (hgstatus.equals(HgFileStatusEnum.DELETED)) {
return FileStatus.DELETED;
}
else if (hgstatus.equals(HgFileStatusEnum.MODIFIED)) {
return FileStatus.MODIFIED;
}
else if (hgstatus.equals(HgFileStatusEnum.COPY)) {
return HgChangeProvider.COPIED;
}
else if (hgstatus.equals(HgFileStatusEnum.UNVERSIONED)) {
return FileStatus.UNKNOWN;
}
else if (hgstatus.equals(HgFileStatusEnum.IGNORED)) {
return FileStatus.IGNORED;
}
else {
return FileStatus.UNKNOWN;
}
}
public static String removePasswordIfNeeded(@NotNull String path) {
Matcher matcher = URL_WITH_PASSWORD.matcher(path);
if (matcher.matches()) {
return path.substring(0, matcher.start(1)) + path.substring(matcher.end(1), path.length());
}
return path;
}
@NotNull
public static String getDisplayableBranchOrBookmarkText(@NotNull HgRepository repository) {
HgRepository.State state = repository.getState();
String branchText = "";
if (state != HgRepository.State.NORMAL) {
branchText += state.toString() + " ";
}
String branchOrBookMarkName = repository.getCurrentBookmark();
if (StringUtil.isEmptyOrSpaces(branchOrBookMarkName)) {
branchOrBookMarkName = repository.getCurrentBranch();
}
return branchText + branchOrBookMarkName;
}
@NotNull
public static HgRepositoryManager getRepositoryManager(@NotNull Project project) {
return ServiceManager.getService(project, HgRepositoryManager.class);
}
@Nullable
public static HgRepository getCurrentRepository(@NotNull Project project) {
VirtualFile file = DvcsUtil.getSelectedFile(project);
return getRepositoryForFile(project, file);
}
@Nullable
public static HgRepository getRepositoryForFile(@NotNull Project project, @Nullable VirtualFile file) {
if (file == null) {
return null;
}
HgRepositoryManager repositoryManager = getRepositoryManager(project);
VirtualFile root = getHgRootOrNull(project, file);
return repositoryManager.getRepositoryForRoot(root);
}
@Nullable
public static String getRepositoryDefaultPath(@NotNull Project project, @NotNull VirtualFile root) {
HgRepository hgRepository = getRepositoryManager(project).getRepositoryForRoot(root);
assert hgRepository != null : "Repository can't be null for root " + root.getName();
return hgRepository.getRepositoryConfig().getDefaultPath();
}
@Nullable
public static String getRepositoryDefaultPushPath(@NotNull Project project, @NotNull VirtualFile root) {
HgRepository hgRepository = getRepositoryManager(project).getRepositoryForRoot(root);
assert hgRepository != null : "Repository can't be null for root " + root.getName();
return hgRepository.getRepositoryConfig().getDefaultPushPath();
}
@Nullable
public static String getRepositoryDefaultPushPath(@NotNull HgRepository repository) {
return repository.getRepositoryConfig().getDefaultPushPath();
}
@Nullable
public static String getConfig(@NotNull Project project,
@NotNull VirtualFile root,
@NotNull String section,
@Nullable String configName) {
HgRepository hgRepository = getRepositoryManager(project).getRepositoryForRoot(root);
assert hgRepository != null : "Repository can't be null for root " + root.getName();
return hgRepository.getRepositoryConfig().getNamedConfig(section, configName);
}
@NotNull
public static Collection<String> getRepositoryPaths(@NotNull Project project,
@NotNull VirtualFile root) {
HgRepository hgRepository = getRepositoryManager(project).getRepositoryForRoot(root);
assert hgRepository != null : "Repository can't be null for root " + root.getName();
return hgRepository.getRepositoryConfig().getPaths();
}
public static boolean isExecutableValid(@Nullable String executable) {
try {
if (StringUtil.isEmptyOrSpaces(executable)) {
return false;
}
HgCommandResult result = getVersionOutput(executable);
return result.getExitValue() == 0 && !result.getRawOutput().isEmpty();
}
catch (Throwable e) {
LOG.info("Error during hg executable validation: ", e);
return false;
}
}
@NotNull
public static HgCommandResult getVersionOutput(@NotNull String executable) throws ShellCommandException, InterruptedException {
String hgExecutable = executable.trim();
List<String> cmdArgs = new ArrayList<String>();
cmdArgs.add(hgExecutable);
cmdArgs.add("version");
cmdArgs.add("-q");
ShellCommand shellCommand = new ShellCommand(cmdArgs, null, CharsetToolkit.getDefaultSystemCharset());
return shellCommand.execute(false);
}
public static List<String> getNamesWithoutHashes(Collection<HgNameWithHashInfo> namesWithHashes) {
//return names without duplication (actually for several heads in one branch)
List<String> names = new ArrayList<String>();
for (HgNameWithHashInfo hash : namesWithHashes) {
if (!names.contains(hash.getName())) {
names.add(hash.getName());
}
}
Collections.sort(names);
return names;
}
@NotNull
public static Pair<String, String> parseUserNameAndEmail(@NotNull String authorString) {
// Vasya Pupkin <vasya.pupkin@jetbrains.com> -> Vasya Pupkin , vasya.pupkin@jetbrains.com
int startEmailIndex = authorString.indexOf('<');
int startDomainIndex = authorString.indexOf('@');
int endEmailIndex = authorString.indexOf('>');
String userName;
String email;
if (0 < startEmailIndex && startEmailIndex < startDomainIndex && startDomainIndex < endEmailIndex) {
email = authorString.substring(startEmailIndex + 1, endEmailIndex);
userName = convertUserName(authorString.substring(0, startEmailIndex));
}
// vasya.pupkin@email.com --> vasya pupkin, vasya.pupkin@email.com
else if (!authorString.contains(" ") && startDomainIndex > 0) { //simple e-mail check. john@localhost
userName = convertUserName(authorString.substring(0, startDomainIndex));
email = authorString;
}
else {
userName = convertUserName(authorString);
email = "";
}
return Pair.create(userName, email);
}
private static String convertUserName(@NotNull String userNameInfo) {
return userNameInfo.trim().replace('.', ' ').replace('_', ' ').replace('-', ' ');
}
}