replaced <code></code> with more concise {@code}
[idea/community.git] / plugins / gradle / src / org / jetbrains / plugins / gradle / util / GradleUtil.java
1 /*
2  * Copyright 2000-2015 JetBrains s.r.o.
3  *
4  * Licensed under the Apache License, Version 2.0 (the "License");
5  * you may not use this file except in compliance with the License.
6  * You may obtain a copy of the License at
7  *
8  * http://www.apache.org/licenses/LICENSE-2.0
9  *
10  * Unless required by applicable law or agreed to in writing, software
11  * distributed under the License is distributed on an "AS IS" BASIS,
12  * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13  * See the License for the specific language governing permissions and
14  * limitations under the License.
15  */
16 package org.jetbrains.plugins.gradle.util;
17
18 import com.intellij.ide.util.PropertiesComponent;
19 import com.intellij.openapi.externalSystem.model.ExternalSystemException;
20 import com.intellij.openapi.externalSystem.util.ExternalSystemApiUtil;
21 import com.intellij.openapi.externalSystem.util.ExternalSystemConstants;
22 import com.intellij.openapi.fileChooser.FileChooserDescriptor;
23 import com.intellij.openapi.fileChooser.FileChooserDescriptorFactory;
24 import com.intellij.openapi.fileChooser.FileTypeDescriptor;
25 import com.intellij.openapi.util.io.FileFilters;
26 import com.intellij.openapi.util.io.FileUtil;
27 import com.intellij.openapi.util.text.StringUtil;
28 import com.intellij.util.containers.ContainerUtilRt;
29 import com.intellij.util.containers.Stack;
30 import org.gradle.tooling.model.GradleProject;
31 import org.gradle.tooling.model.gradle.GradleScript;
32 import org.gradle.wrapper.WrapperConfiguration;
33 import org.gradle.wrapper.WrapperExecutor;
34 import org.jetbrains.annotations.NotNull;
35 import org.jetbrains.annotations.Nullable;
36
37 import java.io.BufferedReader;
38 import java.io.File;
39 import java.io.FileReader;
40 import java.io.IOException;
41 import java.net.URI;
42 import java.util.Arrays;
43 import java.util.Properties;
44
45 /**
46  * Holds miscellaneous utility methods.
47  *
48  * @author Denis Zhdanov
49  * @since 8/25/11 1:19 PM
50  */
51 public class GradleUtil {
52   private static final String LAST_USED_GRADLE_HOME_KEY = "last.used.gradle.home";
53
54   private GradleUtil() { }
55
56   /**
57    * Allows to retrieve file chooser descriptor that filters gradle scripts.
58    * <p/>
59    * <b>Note:</b> we want to fall back to the standard {@link FileTypeDescriptor} when dedicated gradle file type
60    * is introduced (it's processed as groovy file at the moment). We use open project descriptor here in order to show
61    * custom gradle icon at the file chooser ({@link icons.GradleIcons#Gradle}, is used at the file chooser dialog via
62    * the dedicated gradle project open processor).
63    */
64   @NotNull
65   public static FileChooserDescriptor getGradleProjectFileChooserDescriptor() {
66     return FileChooserDescriptorFactory.createSingleFileDescriptor(GradleConstants.EXTENSION);
67   }
68
69   @NotNull
70   public static FileChooserDescriptor getGradleHomeFileChooserDescriptor() {
71     return FileChooserDescriptorFactory.createSingleFolderDescriptor();
72   }
73
74   @SuppressWarnings("IOResourceOpenedButNotSafelyClosed")
75   public static boolean isGradleDefaultWrapperFilesExist(@Nullable String gradleProjectPath) {
76     return getWrapperConfiguration(gradleProjectPath) != null;
77   }
78
79   /**
80    * Tries to retrieve what settings should be used with gradle wrapper for the gradle project located at the given path.
81    *
82    * @param gradleProjectPath  target gradle project config (*.gradle) path or config file's directory path.
83    * @return                   gradle wrapper settings should be used with gradle wrapper for the gradle project located at the given path
84    *                           if any; {@code null} otherwise
85    */
86   @Nullable
87   public static WrapperConfiguration getWrapperConfiguration(@Nullable String gradleProjectPath) {
88     final File wrapperPropertiesFile = findDefaultWrapperPropertiesFile(gradleProjectPath);
89     if (wrapperPropertiesFile == null) return null;
90
91     final WrapperConfiguration wrapperConfiguration = new WrapperConfiguration();
92     final Properties props = new Properties();
93     BufferedReader reader = null;
94     try {
95       reader = new BufferedReader(new FileReader(wrapperPropertiesFile));
96       props.load(reader);
97       String distributionUrl = props.getProperty(WrapperExecutor.DISTRIBUTION_URL_PROPERTY);
98       if(StringUtil.isEmpty(distributionUrl)) {
99         throw new ExternalSystemException("Wrapper 'distributionUrl' property does not exist!");
100       } else {
101         wrapperConfiguration.setDistribution(new URI(distributionUrl));
102       }
103       String distributionPath = props.getProperty(WrapperExecutor.DISTRIBUTION_PATH_PROPERTY);
104       if(!StringUtil.isEmpty(distributionPath)) {
105         wrapperConfiguration.setDistributionPath(distributionPath);
106       }
107       String distPathBase = props.getProperty(WrapperExecutor.DISTRIBUTION_BASE_PROPERTY);
108       if(!StringUtil.isEmpty(distPathBase)) {
109         wrapperConfiguration.setDistributionBase(distPathBase);
110       }
111       return wrapperConfiguration;
112     }
113     catch (Exception e) {
114       GradleLog.LOG.warn(
115         String.format("I/O exception on reading gradle wrapper properties file at '%s'", wrapperPropertiesFile.getAbsolutePath()), e);
116     }
117     finally {
118       if (reader != null) {
119         try {
120           reader.close();
121         }
122         catch (IOException e) {
123           // Ignore
124         }
125       }
126     }
127     return null;
128   }
129
130   /**
131    * Allows to build file system path to the target gradle sub-project given the root project path.
132    *
133    * @param subProject       target sub-project which config path we're interested in
134    * @param rootProjectPath  path to root project's directory which contains 'build.gradle'
135    * @return                 path to the given sub-project's directory which contains 'build.gradle'
136    */
137   @NotNull
138   public static String getConfigPath(@NotNull GradleProject subProject, @NotNull String rootProjectPath) {
139     try {
140       GradleScript script = subProject.getBuildScript();
141       if (script != null) {
142         File file = script.getSourceFile();
143         if (file != null) {
144           if (!file.isDirectory()) {
145             // The file points to 'build.gradle' at the moment but we keep it's parent dir path instead.
146             file = file.getParentFile();
147           }
148           return ExternalSystemApiUtil.toCanonicalPath(file.getCanonicalPath());
149         }
150       }
151     }
152     catch (Exception e) {
153       // As said by gradle team: 'One thing I'm interested in is whether you have any thoughts about how the tooling API should
154       // deal with missing details from the model - for example, when asking for details about the build scripts when using
155       // a version of Gradle that does not supply that information. Currently, you'll get a `UnsupportedOperationException`
156       // when you call the `getBuildScript()` method'.
157       //
158       // So, just ignore it and assume that the user didn't define any custom build file name.
159     }
160     File rootProjectParent = new File(rootProjectPath);
161     StringBuilder buffer = new StringBuilder(FileUtil.toCanonicalPath(rootProjectParent.getAbsolutePath()));
162     Stack<String> stack = ContainerUtilRt.newStack();
163     for (GradleProject p = subProject; p != null; p = p.getParent()) {
164       stack.push(p.getName());
165     }
166
167     // pop root project
168     stack.pop();
169     while (!stack.isEmpty()) {
170       buffer.append(ExternalSystemConstants.PATH_SEPARATOR).append(stack.pop());
171     }
172     return buffer.toString();
173   }
174
175   @NotNull
176   public static String getLastUsedGradleHome() {
177     return PropertiesComponent.getInstance().getValue(LAST_USED_GRADLE_HOME_KEY, "");
178   }
179
180   public static void storeLastUsedGradleHome(@Nullable String gradleHomePath) {
181     PropertiesComponent.getInstance().setValue(LAST_USED_GRADLE_HOME_KEY, gradleHomePath, null);
182   }
183
184   @Nullable
185   public static File findDefaultWrapperPropertiesFile(@Nullable String gradleProjectPath) {
186     if (gradleProjectPath == null) {
187       return null;
188     }
189     File file = new File(gradleProjectPath);
190
191     // There is a possible case that given path points to a gradle script (*.gradle) but it's also possible that
192     // it references script's directory. We want to provide flexibility here.
193     File gradleDir;
194     if (file.isFile()) {
195       gradleDir = new File(file.getParentFile(), "gradle");
196     }
197     else {
198       gradleDir = new File(file, "gradle");
199     }
200     if (!gradleDir.isDirectory()) {
201       return null;
202     }
203
204     File wrapperDir = new File(gradleDir, "wrapper");
205     if (!wrapperDir.isDirectory()) {
206       return null;
207     }
208
209     File[] candidates = wrapperDir.listFiles(FileFilters.filesWithExtension("properties"));
210     if (candidates == null) {
211       GradleLog.LOG.warn("No *.properties file is found at the gradle wrapper directory " + wrapperDir.getAbsolutePath());
212       return null;
213     }
214     else if (candidates.length != 1) {
215       GradleLog.LOG.warn(String.format(
216         "%d *.properties files instead of one have been found at the wrapper directory (%s): %s",
217         candidates.length, wrapperDir.getAbsolutePath(), Arrays.toString(candidates)
218       ));
219       return null;
220     }
221
222     return candidates[0];
223   }
224 }