Java tutorial
/* * Copyright (c) 2018, WSO2 Inc. (http://www.wso2.org) All Rights Reserved. * * 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 io.ballerina.plugins.idea.sdk; import com.google.common.base.Strings; import com.intellij.execution.configurations.PathEnvironmentVariableUtil; import com.intellij.ide.plugins.IdeaPluginDescriptor; import com.intellij.ide.plugins.PluginManager; import com.intellij.openapi.application.ApplicationManager; import com.intellij.openapi.application.ex.ApplicationManagerEx; import com.intellij.openapi.diagnostic.Logger; import com.intellij.openapi.extensions.PluginId; import com.intellij.openapi.module.Module; import com.intellij.openapi.module.ModuleManager; import com.intellij.openapi.project.Project; import com.intellij.openapi.project.ex.ProjectManagerEx; import com.intellij.openapi.projectRoots.Sdk; import com.intellij.openapi.roots.ModuleRootManager; import com.intellij.openapi.roots.ProjectRootManager; import com.intellij.openapi.ui.Messages; import com.intellij.openapi.util.Key; import com.intellij.openapi.util.SystemInfo; import com.intellij.openapi.util.io.FileUtil; import com.intellij.openapi.util.text.StringUtil; import com.intellij.openapi.vfs.LocalFileSystem; import com.intellij.openapi.vfs.VfsUtilCore; import com.intellij.openapi.vfs.VirtualFile; import com.intellij.openapi.vfs.VirtualFileManager; import com.intellij.openapi.vfs.newvfs.impl.VfsRootAccess; import com.intellij.psi.util.CachedValueProvider; import com.intellij.psi.util.CachedValuesManager; import com.intellij.util.Function; import com.intellij.util.ObjectUtils; import com.intellij.util.containers.ContainerUtil; import io.ballerina.plugins.idea.BallerinaConstants; import io.ballerina.plugins.idea.preloading.OSUtils; import io.ballerina.plugins.idea.project.BallerinaApplicationLibrariesService; import io.ballerina.plugins.idea.project.BallerinaLibrariesService; import io.ballerina.plugins.idea.settings.autodetect.BallerinaAutoDetectionSettings; import org.jetbrains.annotations.NotNull; import org.jetbrains.annotations.Nullable; import java.io.File; import java.io.IOException; import java.nio.charset.StandardCharsets; import java.nio.file.Files; import java.nio.file.Paths; import java.util.Collection; import java.util.Collections; import java.util.LinkedHashSet; import java.util.List; import java.util.regex.Matcher; import java.util.stream.Stream; import static com.intellij.util.containers.ContainerUtil.newLinkedHashSet; import static io.ballerina.plugins.idea.BallerinaConstants.BALLERINA_COMPOSER_LIB_PATH; import static io.ballerina.plugins.idea.BallerinaConstants.BALLERINA_CONFIG_FILE_NAME; import static io.ballerina.plugins.idea.BallerinaConstants.BALLERINA_EXECUTABLE_NAME; import static io.ballerina.plugins.idea.BallerinaConstants.BALLERINA_EXEC_PATH; import static io.ballerina.plugins.idea.BallerinaConstants.BALLERINA_LS_LAUNCHER_NAME; import static io.ballerina.plugins.idea.BallerinaConstants.BALLERINA_LS_LAUNCHER_PATH; import static io.ballerina.plugins.idea.BallerinaConstants.BALLERINA_PLUGIN_ID; import static io.ballerina.plugins.idea.BallerinaConstants.BALLERINA_VERSION_PATTERN; import static io.ballerina.plugins.idea.preloading.OSUtils.MAC; import static io.ballerina.plugins.idea.preloading.OSUtils.UNIX; import static io.ballerina.plugins.idea.preloading.OSUtils.WINDOWS; /** * Contains util classes related to Ballerina SDK. */ public class BallerinaSdkUtils { private static final Logger LOG = Logger.getInstance(BallerinaSdkUtils.class); private static final Key<String> VERSION_DATA_KEY = Key.create("BALLERINA_VERSION_KEY"); private static final String INSTALLER_PATH_UNIX = "/usr/bin/ballerina"; private static final String INSTALLER_PATH_MAC = "/etc/paths.d/ballerina"; // Todo private static final String INSTALLER_PATH_WINDOWS = "C:\\Program Files\\Ballerina\\ballerina"; private BallerinaSdkUtils() { } @Nullable public static VirtualFile suggestSdkDirectory() { if (SystemInfo.isWindows) { return ObjectUtils.chooseNotNull(LocalFileSystem.getInstance().findFileByPath("C:\\ballerina"), null); } if (SystemInfo.isMac || SystemInfo.isLinux) { String fromEnv = suggestSdkDirectoryPathFromEnv(); if (fromEnv != null) { return LocalFileSystem.getInstance().findFileByPath(fromEnv); } VirtualFile usrLocal = LocalFileSystem.getInstance().findFileByPath("/usr/local/ballerina"); if (usrLocal != null) { return usrLocal; } } if (SystemInfo.isMac) { String macPorts = "/opt/local/lib/ballerina"; String homeBrew = "/usr/local/Cellar/ballerina"; File file = FileUtil.findFirstThatExist(macPorts, homeBrew); if (file != null) { return LocalFileSystem.getInstance().findFileByIoFile(file); } } return null; } @Nullable private static String suggestSdkDirectoryPathFromEnv() { File fileFromPath = PathEnvironmentVariableUtil.findInPath("ballerina"); if (fileFromPath != null) { File canonicalFile; try { canonicalFile = fileFromPath.getCanonicalFile(); String path = canonicalFile.getPath(); if (path.endsWith(BALLERINA_EXEC_PATH)) { return StringUtil.trimEnd(path, BALLERINA_EXEC_PATH); } } catch (IOException ignore) { } } return null; } @Nullable public static String retrieveBallerinaVersion(@NotNull String sdkPath) { try { // need this hack to avoid an IDEA bug caused by "AssertionError: File accessed outside allowed roots" VfsRootAccess.allowRootAccess(sdkPath); VirtualFile sdkRoot = VirtualFileManager.getInstance().findFileByUrl(VfsUtilCore.pathToUrl(sdkPath)); if (sdkRoot != null) { String cachedVersion = sdkRoot.getUserData(VERSION_DATA_KEY); if (cachedVersion != null) { return !cachedVersion.isEmpty() ? cachedVersion : null; } VirtualFile versionFile = sdkRoot .findFileByRelativePath(BallerinaConstants.BALLERINA_VERSION_FILE_PATH); if (versionFile == null) { VfsRootAccess.allowRootAccess(); versionFile = sdkRoot .findFileByRelativePath(BallerinaConstants.BALLERINA_NEW_VERSION_FILE_PATH); } // Please note that if the above versionFile is null, we can check on other locations as well. if (versionFile != null) { String text = VfsUtilCore.loadText(versionFile); String version = parseBallerinaVersion(text); if (version == null) { BallerinaSdkService.LOG .debug("Cannot retrieve Ballerina version from version file: " + text); } sdkRoot.putUserData(VERSION_DATA_KEY, StringUtil.notNullize(version)); return version; } else { BallerinaSdkService.LOG.debug("Cannot find Ballerina version file in sdk path: " + sdkPath); } } } catch (Exception e) { BallerinaSdkService.LOG.debug("Cannot retrieve Ballerina version from sdk path: " + sdkPath, e); } return null; } @Nullable public static String getBallerinaPluginVersion() { IdeaPluginDescriptor balPluginDescriptor = PluginManager.getPlugin(PluginId.getId(BALLERINA_PLUGIN_ID)); if (balPluginDescriptor != null) { return balPluginDescriptor.getVersion(); } return null; } @NotNull public static String adjustSdkPath(@NotNull String path) { return path; } @Nullable public static String parseBallerinaVersion(@NotNull String text) { Matcher matcher = BALLERINA_VERSION_PATTERN.matcher(text); if (matcher.find()) { return matcher.group(1); } return null; } @NotNull public static Collection<VirtualFile> getSdkDirectoriesToAttach(@NotNull String sdkPath, @NotNull String versionString) { return ContainerUtil.createMaybeSingletonList(getSdkSrcDir(sdkPath, versionString)); } @Nullable private static VirtualFile getSdkSrcDir(@NotNull String sdkPath, @NotNull String sdkVersion) { String srcPath = getSrcLocation(sdkVersion); VirtualFile file = VirtualFileManager.getInstance() .findFileByUrl(VfsUtilCore.pathToUrl(FileUtil.join(sdkPath, srcPath))); return file != null && file.isDirectory() ? file : null; } @NotNull public static BallerinaSdk getBallerinaSdkFor(Project project, Module module) { String sdkPath = BallerinaSdkService.getInstance(project).getSdkHomePath(module); if (sdkPath == null) { return new BallerinaSdk(null, false, false); } if (!isValidSdk(sdkPath)) { return new BallerinaSdk(null, false, false); } boolean langServerSupport = hasLangServerSupport(sdkPath); boolean webviewSupport = hasWebviewSupport(sdkPath); return new BallerinaSdk(sdkPath, langServerSupport, webviewSupport); } @NotNull public static BallerinaSdk getBallerinaSdkFor(Project project) { Sdk projectSdk = ProjectRootManager.getInstance(project).getProjectSdk(); if (projectSdk == null) { return new BallerinaSdk(null, false, false); } String sdkPath = projectSdk.getHomePath(); if (!isValidSdk(sdkPath)) { return new BallerinaSdk(null, false, false); } boolean langServerSupport = hasLangServerSupport(sdkPath); boolean webviewSupport = hasWebviewSupport(sdkPath); return new BallerinaSdk(sdkPath, langServerSupport, webviewSupport); } /** * @return an empty string if it fails to auto-detect the ballerina home automatically. */ public static String autoDetectSdk(Project project) { // Checks for the user-configured auto detection settings. if (!BallerinaAutoDetectionSettings.getInstance(project).getIsAutoDetectionEnabled()) { return ""; } String ballerinaPath = getByCommand("ballerina home"); if (ballerinaPath.isEmpty()) { // Todo - Verify // Tries for default installer based locations since "ballerina" commands might not work // because of the IntelliJ issue of PATH variable might not being identified by the IntelliJ java // runtime. String routerScriptPath = getByDefaultPath(); if (OSUtils.isWindows()) { ballerinaPath = getByCommand(String.format("\"%s\" home", routerScriptPath)); } else { ballerinaPath = getByCommand(String.format("%s home", routerScriptPath)); } } return ballerinaPath; } private static String getByCommand(String cmd) { java.util.Scanner s; try { // This may returns a symlink which links to the real path. s = new java.util.Scanner(Runtime.getRuntime().exec(cmd).getInputStream()).useDelimiter("\\A"); String path = s.hasNext() ? s.next().trim().replace(System.lineSeparator(), "").trim() : ""; LOG.info(cmd + "command returned: " + path); // Todo - verify // Since errors might be coming from the input stream when retrieving ballerina distribution path, we need // an additional check. if (!new File(path).exists()) { LOG.warn( String.format("Invalid result received by \"%s\" command. Received Output: %s", cmd, path)); return ""; } return path; } catch (Exception e) { LOG.warn("Unexpected error occurred when executing ballerina command: " + cmd, e); return ""; } } /** * Tries for default installer based locations since "ballerina" commands might not work * because of the IntelliJ issue of PATH variable might not being identified by the IntelliJ java * runtime. */ private static String getByDefaultPath() { try { String path = getDefaultPath(); if (path.isEmpty()) { return path; } // Resolves actual path using "toRealPath()". return new File(path).toPath().toRealPath().toString(); } catch (Exception e) { LOG.warn("Error occurred when resolving symlinks to auto detect ballerina home."); return ""; } } private static String getDefaultPath() { String os = OSUtils.getOperatingSystem(); switch (os) { case UNIX: return INSTALLER_PATH_UNIX; case MAC: // Reads the file content to get the ballerina home location. return getContentAsString(INSTALLER_PATH_MAC); case WINDOWS: // Tries to get the ballerina router script path using the default installation location. return getWinDefaultPath(INSTALLER_PATH_WINDOWS); default: return ""; } } private static String getWinDefaultPath(String defaultDir) { try { String pluginVersion = getBallerinaPluginVersion(); if (pluginVersion == null) { return ""; } String routerPath = String.join("-", defaultDir, pluginVersion); return Paths.get(routerPath, "bin", "ballerina.bat").toString(); } catch (Exception e) { LOG.warn("Error occurred when trying to auto detect using default installation path.", e); return ""; } } private static String getContentAsString(String filePath) { try { Stream<String> stream = Files.lines(Paths.get(filePath), StandardCharsets.UTF_8); StringBuilder contentBuilder = new StringBuilder(); stream.forEach(s -> contentBuilder.append(s).append("\n")); String balHomePath = contentBuilder.toString().trim(); // Append "/ballerina" to content since we only need the ballerina home root. balHomePath = !Strings.isNullOrEmpty(balHomePath) && balHomePath.endsWith("/bin") ? balHomePath.replace("/bin", "/bin/ballerina") : ""; return balHomePath; } catch (Exception ignored) { return ""; } } private static boolean isValidSdk(String sdkPath) { if (Strings.isNullOrEmpty(sdkPath)) { return false; } // Checks for either shell scripts or batch files, since the shell script recognition error in windows. String balShellScript = Paths.get(sdkPath, "bin", BALLERINA_EXECUTABLE_NAME).toString(); String balBatchScript = Paths.get(sdkPath, "bin", BALLERINA_EXECUTABLE_NAME).toString() + ".bat"; return (new File(balShellScript).exists() || new File(balBatchScript).exists()); } private static boolean hasLangServerSupport(String sdkPath) { if (Strings.isNullOrEmpty(sdkPath)) { return false; } // Checks for either shell scripts or batch files, since the shell script recognition error in windows. String launcherShellScript = Paths.get(sdkPath, BALLERINA_LS_LAUNCHER_PATH, BALLERINA_LS_LAUNCHER_NAME) .toString() + ".sh"; String launcherBatchScript = Paths.get(sdkPath, BALLERINA_LS_LAUNCHER_PATH, BALLERINA_LS_LAUNCHER_NAME) .toString() + ".bat"; return new File(launcherShellScript).exists() || new File(launcherBatchScript).exists(); } private static boolean hasWebviewSupport(String sdkPath) { if (Strings.isNullOrEmpty(sdkPath)) { return false; } // Checks for composer library resource directory existence. String composerLib = Paths.get(sdkPath, BALLERINA_COMPOSER_LIB_PATH).toString(); return new File(composerLib).exists(); } public static LinkedHashSet<VirtualFile> getSourcesPathsToLookup(@NotNull Project project, @Nullable Module module) { LinkedHashSet<VirtualFile> sdkAndBallerinaPath = newLinkedHashSet(); ContainerUtil.addIfNotNull(sdkAndBallerinaPath, getSdkSrcDir(project, module)); // Todo - add Ballerina Path // ContainerUtil.addAllNotNull(sdkAndBallerinaPath, getBallerinaPathSources(project, module)); return sdkAndBallerinaPath; } @NotNull private static String getSrcLocation(@NotNull String version) { return "src"; } public static String getSdkHome(Project project, Module module) { // Get the module SDK. Sdk moduleSdk = ModuleRootManager.getInstance(module).getSdk(); // If the SDK is Ballerina SDK, return the home path. if (moduleSdk != null && moduleSdk.getSdkType() == BallerinaSdkType.getInstance()) { return moduleSdk.getHomePath(); } // Ge the project SDK. Sdk projectSdk = ProjectRootManager.getInstance(project).getProjectSdk(); // If the SDK is Ballerina SDK, return the home path. if (projectSdk != null && projectSdk.getSdkType() == BallerinaSdkType.getInstance()) { return projectSdk.getHomePath(); } return ""; } @NotNull public static Collection<VirtualFile> getBallerinaPathsRootsFromEnvironment() { return BallerinaPathModificationTracker.getBallerinaEnvironmentPathRoots(); } @NotNull private static List<VirtualFile> getInnerBallerinaPathSources(@NotNull Project project, @Nullable Module module) { return ContainerUtil.mapNotNull(getBallerinaPathRoots(project, module), new RetrieveSubDirectoryOrSelfFunction("src")); } @NotNull public static Collection<VirtualFile> getBallerinaPathRoots(@NotNull Project project, @Nullable Module module) { Collection<VirtualFile> roots = ContainerUtil.newArrayList(); if (BallerinaApplicationLibrariesService.getInstance().isUseBallerinaPathFromSystemEnvironment()) { roots.addAll(getBallerinaPathsRootsFromEnvironment()); } roots.addAll(module != null ? BallerinaLibrariesService.getUserDefinedLibraries(module) : BallerinaLibrariesService.getUserDefinedLibraries(project)); return roots; } private static class RetrieveSubDirectoryOrSelfFunction implements Function<VirtualFile, VirtualFile> { @NotNull private final String mySubdirName; public RetrieveSubDirectoryOrSelfFunction(@NotNull String subdirName) { mySubdirName = subdirName; } @Override public VirtualFile fun(VirtualFile file) { return file == null || FileUtil.namesEqual(mySubdirName, file.getName()) ? file : file.findChild(mySubdirName); } } @NotNull public static Collection<Module> getBallerinaModules(@NotNull Project project) { if (project.isDefault()) { return Collections.emptyList(); } BallerinaSdkService sdkService = BallerinaSdkService.getInstance(project); return ContainerUtil.filter(ModuleManager.getInstance(project).getModules(), sdkService::isBallerinaModule); } @Nullable public static VirtualFile getSdkSrcDir(@NotNull Project project, @Nullable Module module) { if (module != null) { return CachedValuesManager.getManager(project).getCachedValue(module, () -> { BallerinaSdkService sdkService = BallerinaSdkService.getInstance(module.getProject()); return CachedValueProvider.Result.create(getInnerSdkSrcDir(sdkService, module), sdkService); }); } return CachedValuesManager.getManager(project).getCachedValue(project, () -> { BallerinaSdkService sdkService = BallerinaSdkService.getInstance(project); return CachedValueProvider.Result.create(getInnerSdkSrcDir(sdkService, null), sdkService); }); } /** * Searches for a ballerina project root using outward recursion starting from the file directory, until the given * root directory is found. Returns and empty string if unable to detect any ballerina project under the current * intellij project source root. */ public static String searchForBallerinaProjectRoot(String currentPath, String root) { if (currentPath.isEmpty() || root.isEmpty()) { return ""; } File currentDir = new File(currentPath); File[] files = currentDir.listFiles(); if (files != null) { for (File f : files) { // Searches for the ballerina config file (Ballerina.toml). if (f.isFile() && f.getName().equals(BALLERINA_CONFIG_FILE_NAME)) { return currentDir.getAbsolutePath(); } } } if (currentPath.equals(root) || currentDir.getParentFile() == null) { return ""; } return searchForBallerinaProjectRoot(currentDir.getParentFile().getAbsolutePath(), root); } @Messages.YesNoResult public static void showRestartDialog(Project project) { ApplicationManager.getApplication().invokeLater(() -> { String action = project != null && ProjectManagerEx.getInstanceEx().canClose(project) ? "Reload Project" : "Restart IDE"; String message = "Project/IDE reloading action is required to apply changes. Do you wish to continue?"; if (Messages.showYesNoDialog(message, "Apply Changes", action, "Postpone", Messages.getQuestionIcon()) == Messages.YES) { if (action.equals("Reload Project")) { ProjectManagerEx.getInstanceEx().reloadProject(project); } else { ApplicationManagerEx.getApplicationEx().restart(true); } } }); } @Nullable private static VirtualFile getInnerSdkSrcDir(@NotNull BallerinaSdkService sdkService, @Nullable Module module) { String sdkHomePath = sdkService.getSdkHomePath(module); String sdkVersionString = sdkService.getSdkVersion(module); return sdkHomePath != null && sdkVersionString != null ? getSdkSrcDir(sdkHomePath, sdkVersionString) : null; } }