de2129f05aa2d6d00e2d7b94be5d2c7c0b0d7bbe
[idea/community.git] / platform / external-system-api / src / com / intellij / openapi / externalSystem / util / ExternalSystemApiUtil.java
1 /*
2  * Copyright 2000-2013 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 com.intellij.openapi.externalSystem.util;
17
18 import com.intellij.ide.util.PropertiesComponent;
19 import com.intellij.openapi.application.ApplicationManager;
20 import com.intellij.openapi.application.PathManager;
21 import com.intellij.openapi.diagnostic.Logger;
22 import com.intellij.openapi.externalSystem.ExternalSystemManager;
23 import com.intellij.openapi.externalSystem.model.DataNode;
24 import com.intellij.openapi.externalSystem.model.Key;
25 import com.intellij.openapi.externalSystem.model.ProjectSystemId;
26 import com.intellij.openapi.fileTypes.FileTypes;
27 import com.intellij.openapi.project.ProjectManager;
28 import com.intellij.openapi.roots.OrderRootType;
29 import com.intellij.openapi.roots.libraries.Library;
30 import com.intellij.openapi.util.AtomicNotNullLazyValue;
31 import com.intellij.openapi.util.NotNullLazyValue;
32 import com.intellij.openapi.util.io.FileUtil;
33 import com.intellij.openapi.util.text.StringUtil;
34 import com.intellij.openapi.vfs.JarFileSystem;
35 import com.intellij.openapi.vfs.VirtualFile;
36 import com.intellij.util.BooleanFunction;
37 import com.intellij.util.Function;
38 import com.intellij.util.PathUtil;
39 import com.intellij.util.PathsList;
40 import com.intellij.util.containers.ContainerUtilRt;
41 import com.intellij.util.ui.UIUtil;
42 import org.jetbrains.annotations.NotNull;
43 import org.jetbrains.annotations.Nullable;
44
45 import java.io.File;
46 import java.util.*;
47 import java.util.regex.Matcher;
48 import java.util.regex.Pattern;
49
50 /**
51  * @author Denis Zhdanov
52  * @since 4/1/13 1:31 PM
53  */
54 public class ExternalSystemApiUtil {
55
56   private static final Logger LOG = Logger.getInstance("#" + ExternalSystemApiUtil.class.getName());
57   private static final String LAST_USED_PROJECT_PATH_PREFIX = "LAST_EXTERNAL_PROJECT_PATH_";
58
59   @NotNull public static final String PATH_SEPARATOR = "/";
60
61   @NotNull private static final Pattern ARTIFACT_PATTERN = Pattern.compile("(?:.*/)?(.+?)(?:-([\\d+](?:\\.[\\d]+)*))?(?:\\.[^\\.]+?)?");
62
63   @NotNull private static final NotNullLazyValue<Map<ProjectSystemId, ExternalSystemManager<?, ?, ?, ?, ?>>> MANAGERS =
64     new AtomicNotNullLazyValue<Map<ProjectSystemId, ExternalSystemManager<?, ?, ?, ?, ?>>>() {
65       @NotNull
66       @Override
67       protected Map<ProjectSystemId, ExternalSystemManager<?, ?, ?, ?, ?>> compute() {
68         Map<ProjectSystemId, ExternalSystemManager<?, ?, ?, ?, ?>> result = ContainerUtilRt.newHashMap();
69         for (ExternalSystemManager manager : ExternalSystemManager.EP_NAME.getExtensions()) {
70           result.put(manager.getSystemId(), manager);
71         }
72         return result;
73       }
74     };
75
76   @NotNull public static final Comparator<Object> ORDER_AWARE_COMPARATOR = new Comparator<Object>() {
77     @Override
78     public int compare(Object o1, Object o2) {
79       int order1 = getOrder(o1);
80       int order2 = getOrder(o2);
81       return order1 > order2 ? 1 : order1 < order2 ? -1 : 0;
82     }
83
84     private int getOrder(@NotNull Object o) {
85       Queue<Class<?>> toCheck = new ArrayDeque<Class<?>>();
86       toCheck.add(o.getClass());
87       while (!toCheck.isEmpty()) {
88         Class<?> clazz = toCheck.poll();
89         Order annotation = clazz.getAnnotation(Order.class);
90         if (annotation != null) {
91           return annotation.value();
92         }
93         toCheck.add(clazz.getSuperclass());
94         Class<?>[] interfaces = clazz.getInterfaces();
95         if (interfaces != null) {
96           Collections.addAll(toCheck, interfaces);
97         }
98       }
99       return ExternalSystemConstants.UNORDERED;
100     }
101   };
102
103
104   private ExternalSystemApiUtil() {
105   }
106
107   @NotNull
108   public static String extractNameFromPath(@NotNull String path) {
109     String strippedPath = stripPath(path);
110     final int i = strippedPath.lastIndexOf(PATH_SEPARATOR);
111     final String result;
112     if (i < 0 || i >= strippedPath.length() - 1) {
113       result = strippedPath;
114     }
115     else {
116       result = strippedPath.substring(i + 1);
117     }
118     return result;
119   }
120
121   @NotNull
122   private static String stripPath(@NotNull String path) {
123     String[] endingsToStrip = {"/", "!", ".jar"};
124     StringBuilder buffer = new StringBuilder(path);
125     for (String ending : endingsToStrip) {
126       if (buffer.lastIndexOf(ending) == buffer.length() - ending.length()) {
127         buffer.setLength(buffer.length() - ending.length());
128       }
129     }
130     return buffer.toString();
131   }
132
133   @NotNull
134   public static String getLibraryName(@NotNull Library library) {
135     final String result = library.getName();
136     if (result != null) {
137       return result;
138     }
139     for (OrderRootType type : OrderRootType.getAllTypes()) {
140       for (String url : library.getUrls(type)) {
141         String candidate = extractNameFromPath(url);
142         if (!StringUtil.isEmpty(candidate)) {
143           return candidate;
144         }
145       }
146     }
147     assert false;
148     return "unknown-lib";
149   }
150
151   @Nullable
152   public static ArtifactInfo parseArtifactInfo(@NotNull String fileName) {
153     Matcher matcher = ARTIFACT_PATTERN.matcher(fileName);
154     if (!matcher.matches()) {
155       return null;
156     }
157     return new ArtifactInfo(matcher.group(1), null, matcher.group(2));
158   }
159
160   public static void orderAwareSort(@NotNull List<?> data) {
161     Collections.sort(data, ORDER_AWARE_COMPARATOR);
162   }
163
164   /**
165    * @param path    target path
166    * @return absolute path that points to the same location as the given one and that uses only slashes
167    */
168   @NotNull
169   public static String toCanonicalPath(@NotNull String path) {
170     return PathUtil.getCanonicalPath(new File(path).getAbsolutePath());
171   }
172
173   @NotNull
174   public static String getLocalFileSystemPath(@NotNull VirtualFile file) {
175     if (file.getFileType() == FileTypes.ARCHIVE) {
176       final VirtualFile jar = JarFileSystem.getInstance().getVirtualFileForJar(file);
177       if (jar != null) {
178         return jar.getPath();
179       }
180     }
181     return file.getPath();
182   }
183
184   @Nullable
185   public static ExternalSystemManager<?, ?, ?, ?, ?> getManager(@NotNull ProjectSystemId externalSystemId) {
186     return MANAGERS.getValue().get(externalSystemId);
187   }
188
189   public static Collection<ExternalSystemManager<?, ?, ?, ?, ?>> getAllManagers() {
190     return MANAGERS.getValue().values();
191   }
192
193   @NotNull
194   public static Map<Key<?>, List<DataNode<?>>> group(@NotNull Collection<DataNode<?>> nodes) {
195     if (nodes.isEmpty()) {
196       return Collections.emptyMap();
197     }
198     Map<Key<?>, List<DataNode<?>>> result = ContainerUtilRt.newHashMap();
199     for (DataNode<?> node : nodes) {
200       List<DataNode<?>> n = result.get(node.getKey());
201       if (n == null) {
202         result.put(node.getKey(), n = ContainerUtilRt.newArrayList());
203       }
204       n.add(node);
205     }
206     return result;
207   }
208
209   @NotNull
210   public static <K, V> Map<DataNode<K>, List<DataNode<V>>> groupBy(@NotNull Collection<DataNode<V>> nodes, @NotNull final Key<K> key) {
211     return groupBy(nodes, new Function<DataNode<V>, DataNode<K>>() {
212       @Override
213       public DataNode<K> fun(DataNode<V> node) {
214         return node.getDataNode(key);
215       }
216     });
217   }
218
219   @NotNull
220   public static <K, V> Map<K, List<V>> groupBy(@NotNull Collection<V> nodes, @NotNull Function<V, K> grouper) {
221     Map<K, List<V>> result = ContainerUtilRt.newHashMap();
222     for (V data : nodes) {
223       K key = grouper.fun(data);
224       if (key == null) {
225         LOG.warn(String.format(
226           "Skipping entry '%s' during grouping. Reason: it's not possible to build a grouping key with grouping strategy '%s'. "
227           + "Given entries: %s",
228           data,
229           grouper.getClass(),
230           nodes));
231         continue;
232       }
233       List<V> grouped = result.get(key);
234       if (grouped == null) {
235         result.put(key, grouped = ContainerUtilRt.newArrayList());
236       }
237       grouped.add(data);
238     }
239     return result;
240   }
241
242   @SuppressWarnings("unchecked")
243   @NotNull
244   public static <T> Collection<DataNode<T>> getChildren(@NotNull DataNode<?> node, @NotNull Key<T> key) {
245     Collection<DataNode<T>> result = null;
246     for (DataNode<?> child : node.getChildren()) {
247       if (!key.equals(child.getKey())) {
248         continue;
249       }
250       if (result == null) {
251         result = ContainerUtilRt.newArrayList();
252       }
253       result.add((DataNode<T>)child);
254     }
255     return result == null ? Collections.<DataNode<T>>emptyList() : result;
256   }
257
258   @SuppressWarnings("unchecked")
259   @Nullable
260   public static <T> DataNode<T> find(@NotNull DataNode<?> node, @NotNull Key<T> key) {
261     for (DataNode<?> child : node.getChildren()) {
262       if (key.equals(child.getKey())) {
263         return (DataNode<T>)child;
264       }
265     }
266     return null;
267   }
268
269   @SuppressWarnings("unchecked")
270   @Nullable
271   public static <T> DataNode<T> find(@NotNull DataNode<?> node, @NotNull Key<T> key, BooleanFunction<DataNode<T>> predicate) {
272     for (DataNode<?> child : node.getChildren()) {
273       if (key.equals(child.getKey()) && predicate.fun((DataNode<T>)child)) {
274         return (DataNode<T>)child;
275       }
276     }
277     return null;
278   }
279
280   @SuppressWarnings("unchecked")
281   @NotNull
282   public static <T> Collection<DataNode<T>> findAll(@NotNull DataNode<?> parent, @NotNull Key<T> key) {
283     Collection<DataNode<T>> result = null;
284     for (DataNode<?> child : parent.getChildren()) {
285       if (!key.equals(child.getKey())) {
286         continue;
287       }
288       if (result == null) {
289         result = ContainerUtilRt.newArrayList();
290       }
291       result.add((DataNode<T>)child);
292     }
293     return result == null ? Collections.<DataNode<T>>emptyList() : result;
294   }
295
296   public static void executeProjectChangeAction(@NotNull final Runnable task) {
297     executeProjectChangeAction(false, task);
298   }
299
300   public static void executeProjectChangeAction(boolean synchronous, @NotNull final Runnable task) {
301     executeOnEdt(synchronous, new Runnable() {
302       public void run() {
303         ApplicationManager.getApplication().runWriteAction(new Runnable() {
304           @Override
305           public void run() {
306             task.run();
307           }
308         });
309       }
310     });
311   }
312   
313   public static void executeOnEdt(boolean synchronous, @NotNull Runnable task) {
314     if (synchronous) {
315       if (ApplicationManager.getApplication().isDispatchThread()) {
316         task.run();
317       }
318       else {
319         UIUtil.invokeAndWaitIfNeeded(task);
320       }
321     }
322     else {
323       UIUtil.invokeLaterIfNeeded(task);
324     }
325   }
326
327   /**
328    * Configures given classpath to reference target i18n bundle file(s).
329    *
330    * @param classPath     process classpath
331    * @param bundlePath    path to the target bundle file
332    * @param contextClass  class from the same content root as the target bundle file
333    */
334   public static void addBundle(@NotNull PathsList classPath, @NotNull String bundlePath, @NotNull Class<?> contextClass) {
335     String pathToUse = bundlePath.replace('.', '/');
336     if (!pathToUse.endsWith(".properties")) {
337       pathToUse += ".properties";
338     }
339     if (!pathToUse.startsWith("/")) {
340       pathToUse = '/' + pathToUse;
341     }
342     classPath.add(PathManager.getResourceRoot(contextClass, pathToUse));
343   }
344
345   @SuppressWarnings("ConstantConditions")
346   public static String normalizePath(String s) {
347     return StringUtil.isEmpty(s) ? null : s.replace('\\', ExternalSystemConstants.PATH_SEPARATOR);
348   }
349
350   /**
351    * We can divide all 'import from external system' use-cases into at least as below:
352    * <pre>
353    * <ul>
354    *   <li>this is a new project being created (import project from external model);</li>
355    *   <li>a new module is being imported from an external project into an existing ide project;</li>
356    * </ul>
357    * </pre>
358    * This method allows to differentiate between them (e.g. we don't want to change language level when new module is imported to
359    * an existing project).
360    * 
361    * @return    <code>true</code> if new project is being imported; <code>false</code> if new module is being imported
362    */
363   public static boolean isNewProjectConstruction() {
364     return ProjectManager.getInstance().getOpenProjects().length == 0;
365   }
366
367   @NotNull
368   public static String getLastUsedExternalProjectPath(@NotNull ProjectSystemId externalSystemId) {
369     return PropertiesComponent.getInstance().getValue(LAST_USED_PROJECT_PATH_PREFIX + externalSystemId.getReadableName(), "");
370   }
371
372   public static void storeLastUsedExternalProjectPath(@Nullable String path, @NotNull ProjectSystemId externalSystemId) {
373     if (path != null) {
374       PropertiesComponent.getInstance().setValue(LAST_USED_PROJECT_PATH_PREFIX + externalSystemId.getReadableName(), path);
375     }
376   }
377
378   @NotNull
379   public static String getProjectRepresentationName(@NotNull String targetProjectPath, @Nullable String rootProjectPath) {
380     if (rootProjectPath == null) {
381       return new File(targetProjectPath).getParentFile().getName();
382     }
383     File rootProjectDir = new File(rootProjectPath).getParentFile();
384     StringBuilder buffer = new StringBuilder();
385     for (File f = new File(targetProjectPath).getParentFile(); f != null && !FileUtil.filesEqual(f, rootProjectDir); f = f.getParentFile()) {
386       buffer.insert(0, f.getName()).insert(0, ":");
387     }
388     buffer.insert(0, rootProjectDir.getName());
389     return buffer.toString();
390   }
391 }