Merge branch 'master' of git.labs.intellij.net:idea/community
[idea/community.git] / plugins / eclipse / src / org / jetbrains / idea / eclipse / conversion / EclipseClasspathReader.java
1 /*
2  * Copyright 2000-2009 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
17 /*
18  * User: anna
19  * Date: 11-Nov-2008
20  */
21 package org.jetbrains.idea.eclipse.conversion;
22
23 import com.intellij.openapi.components.PathMacroManager;
24 import com.intellij.openapi.module.Module;
25 import com.intellij.openapi.module.ModuleManager;
26 import com.intellij.openapi.project.Project;
27 import com.intellij.openapi.projectRoots.ProjectJdkTable;
28 import com.intellij.openapi.projectRoots.Sdk;
29 import com.intellij.openapi.projectRoots.ex.JavaSdkUtil;
30 import com.intellij.openapi.roots.*;
31 import com.intellij.openapi.roots.libraries.Library;
32 import com.intellij.openapi.roots.libraries.LibraryTable;
33 import com.intellij.openapi.roots.libraries.LibraryTablesRegistrar;
34 import com.intellij.openapi.util.Comparing;
35 import com.intellij.openapi.vfs.JarFileSystem;
36 import com.intellij.openapi.vfs.VfsUtil;
37 import com.intellij.openapi.vfs.VirtualFile;
38 import com.intellij.openapi.vfs.VirtualFileManager;
39 import com.intellij.util.ArrayUtil;
40 import org.jdom.Element;
41 import org.jetbrains.annotations.NotNull;
42 import org.jetbrains.annotations.Nullable;
43 import org.jetbrains.idea.eclipse.EclipseXml;
44 import org.jetbrains.idea.eclipse.IdeaXml;
45 import org.jetbrains.idea.eclipse.config.EclipseModuleManager;
46 import org.jetbrains.idea.eclipse.util.ErrorLog;
47
48 import java.io.File;
49 import java.io.IOException;
50 import java.util.ArrayList;
51 import java.util.Collection;
52 import java.util.List;
53 import java.util.Set;
54
55 import static org.jetbrains.idea.eclipse.conversion.ERelativePathUtil.*;
56
57 public class EclipseClasspathReader {
58   private final String myRootPath;
59   private final Project myProject;
60   @Nullable private final List<String> myCurrentRoots;
61   private ContentEntry myContentEntry;
62
63   public EclipseClasspathReader(final String rootPath, final Project project, @Nullable List<String> currentRoots) {
64     myRootPath = rootPath;
65     myProject = project;
66     myCurrentRoots = currentRoots;
67   }
68
69   public void init(ModifiableRootModel model) {
70     myContentEntry = model.addContentEntry(VfsUtil.pathToUrl(myRootPath));
71   }
72
73   public static void collectVariables(Set<String> usedVariables, Element classpathElement) {
74     for (Object o : classpathElement.getChildren(EclipseXml.CLASSPATHENTRY_TAG)) {
75       final Element element = (Element)o;
76       final String kind = element.getAttributeValue(EclipseXml.KIND_ATTR);
77       if (Comparing.strEqual(kind, EclipseXml.VAR_KIND)) {
78         String path = element.getAttributeValue(EclipseXml.PATH_ATTR);
79         if (path == null) continue;
80         int slash = path.indexOf("/");
81         if (slash > 0) {
82           usedVariables.add(path.substring(0, slash));
83         }
84         else {
85           usedVariables.add(path);
86         }
87
88
89         final String srcPath = element.getAttributeValue(EclipseXml.SOURCEPATH_ATTR);
90         if (srcPath == null) continue;
91         final int varStart = srcPath.startsWith("/") ? 1 : 0;
92         final int slash2 = srcPath.indexOf("/", varStart);
93         if (slash2 > 0) {
94           usedVariables.add(srcPath.substring(varStart, slash2));
95         }
96         else {
97           usedVariables.add(srcPath.substring(varStart));
98         }
99       }
100     }
101   }
102
103   public void readClasspath(ModifiableRootModel model,
104                             final Collection<String> unknownLibraries,
105                             Collection<String> unknownJdks,
106                             final Set<String> usedVariables,
107                             Set<String> refsToModules,
108                             final String testPattern,
109                             Element classpathElement) throws IOException, ConversionException {
110     for (OrderEntry orderEntry : model.getOrderEntries()) {
111       if (!(orderEntry instanceof ModuleSourceOrderEntry)) {
112         model.removeOrderEntry(orderEntry);
113       }
114     }
115     for (Object o : classpathElement.getChildren(EclipseXml.CLASSPATHENTRY_TAG)) {
116       try {
117         readClasspathEntry(model, unknownLibraries, unknownJdks, usedVariables, refsToModules, testPattern, (Element)o);
118       }
119       catch (ConversionException e) {
120         ErrorLog.rethrow(ErrorLog.Level.Warning, null, EclipseXml.CLASSPATH_FILE, e);
121       }
122     }
123     if (!model.isSdkInherited() && model.getSdkName() == null) {
124       EclipseModuleManager.getInstance(model.getModule()).setForceConfigureJDK();
125       model.inheritSdk();
126     }
127   }
128
129   private void readClasspathEntry(ModifiableRootModel rootModel,
130                                   final Collection<String> unknownLibraries,
131                                   Collection<String> unknownJdks,
132                                   final Set<String> usedVariables,
133                                   Set<String> refsToModules,
134                                   final String testPattern,
135                                   Element element) throws ConversionException {
136     String kind = element.getAttributeValue(EclipseXml.KIND_ATTR);
137     if (kind == null) {
138       throw new ConversionException("Missing classpathentry/@kind");
139     }
140
141
142     String path = element.getAttributeValue(EclipseXml.PATH_ATTR);
143     if (path == null) {
144       throw new ConversionException("Missing classpathentry/@path");
145     }
146
147     final boolean exported = EclipseXml.TRUE_VALUE.equals(element.getAttributeValue(EclipseXml.EXPORTED_ATTR));
148
149     if (kind.equals(EclipseXml.SRC_KIND)) {
150       if (path.startsWith("/")) {
151         final String moduleName = path.substring(1);
152         refsToModules.add(moduleName);
153         rootModel.addInvalidModuleEntry(moduleName).setExported(exported);
154       }
155       else {
156         getContentEntry().addSourceFolder(VfsUtil.pathToUrl(myRootPath + "/" + path), testPattern != null && testPattern.length() > 0 && path.matches(testPattern));
157         rearrangeOrderEntryOfType(rootModel, ModuleSourceOrderEntry.class);
158       }
159     }
160
161     else if (kind.equals(EclipseXml.OUTPUT_KIND)) {
162       setupOutput(rootModel, myRootPath + "/" + path);
163     }
164
165     else if (kind.equals(EclipseXml.LIB_KIND)) {
166       final String libName = getPresentableName(path);
167       final Library library = rootModel.getModuleLibraryTable().getModifiableModel().createLibrary(libName);
168       final Library.ModifiableModel modifiableModel = library.getModifiableModel();
169
170       modifiableModel.addRoot(getUrl(path, rootModel), OrderRootType.CLASSES);
171
172       final String sourcePath = element.getAttributeValue(EclipseXml.SOURCEPATH_ATTR);
173       if (sourcePath != null) {
174         modifiableModel.addRoot(getUrl(sourcePath, rootModel), OrderRootType.SOURCES);
175       }
176
177       final List<String> docPaths = EJavadocUtil.getJavadocAttribute(element, rootModel, myCurrentRoots);
178       if (docPaths != null) {
179         for (String docPath : docPaths) {
180           modifiableModel.addRoot(docPath, JavadocOrderRootType.getInstance());
181         }
182       }
183
184       modifiableModel.commit();
185
186       setLibraryEntryExported(rootModel, exported, library);
187     }
188     else if (kind.equals(EclipseXml.VAR_KIND)) {
189       int slash = path.indexOf("/");
190       if (slash == 0) {
191         throw new ConversionException("Incorrect 'classpathentry/var@path' format");
192       }
193
194       final String libName = getPresentableName(path);
195       final Library library = rootModel.getModuleLibraryTable().getModifiableModel().createLibrary(libName);
196       final Library.ModifiableModel modifiableModel = library.getModifiableModel();
197
198
199       final String clsVar;
200       final String clsPath;
201       if (slash > 0) {
202         clsVar = path.substring(0, slash);
203         clsPath = path.substring(slash + 1);
204       }
205       else {
206         clsVar = path;
207         clsPath = null;
208       }
209       usedVariables.add(clsVar);
210
211       final String url = getUrl(PathMacroManager.getInstance(rootModel.getModule()).expandPath(getVariableRelatedPath(clsVar, clsPath)),
212                                 rootModel);
213       EclipseModuleManager.getInstance(rootModel.getModule()).registerEclipseVariablePath(url, path);
214       modifiableModel.addRoot(url, OrderRootType.CLASSES);
215
216       final String srcPathAttr = element.getAttributeValue(EclipseXml.SOURCEPATH_ATTR);
217       if (srcPathAttr != null) {
218         final String srcVar;
219         final String srcPath;
220         final int varStart = srcPathAttr.startsWith("/") ? 1 : 0;
221
222         int slash2 = srcPathAttr.indexOf("/", varStart);
223         if (slash2 > 0) {
224           srcVar = srcPathAttr.substring(varStart, slash2);
225           srcPath = srcPathAttr.substring(slash2 + 1);
226         }
227         else {
228           srcVar = srcPathAttr.substring(varStart);
229           srcPath = null;
230         }
231         usedVariables.add(srcVar);
232         final String srcUrl = getUrl(PathMacroManager.getInstance(rootModel.getModule()).expandPath(getVariableRelatedPath(srcVar, srcPath)),
233                                      rootModel);
234         EclipseModuleManager.getInstance(rootModel.getModule()).registerEclipseSrcVariablePath(srcUrl, srcPathAttr);
235         modifiableModel.addRoot(srcUrl, OrderRootType.SOURCES);
236       }
237
238       final List<String> docPaths = EJavadocUtil.getJavadocAttribute(element, rootModel, myCurrentRoots);
239       if (docPaths != null) {
240         for (String docPath : docPaths) {
241           modifiableModel.addRoot(docPath, JavadocOrderRootType.getInstance());
242         }
243       }
244
245       modifiableModel.commit();
246
247       setLibraryEntryExported(rootModel, exported, library);
248     }
249     else if (kind.equals(EclipseXml.CON_KIND)) {
250       if (path.equals(EclipseXml.ECLIPSE_PLATFORM)) {
251         addNamedLibrary(rootModel, unknownLibraries, exported, IdeaXml.ECLIPSE_LIBRARY, LibraryTablesRegistrar.APPLICATION_LEVEL);
252       }
253       else if (path.startsWith(EclipseXml.JRE_CONTAINER)) {
254
255         final String jdkName = getLastPathComponent(path);
256         if (jdkName == null) {
257           rootModel.inheritSdk();
258         }
259         else {
260           final Sdk moduleJdk = ProjectJdkTable.getInstance().findJdk(jdkName);
261           if (moduleJdk != null) {
262             rootModel.setSdk(moduleJdk);
263           }
264           else {
265             rootModel.setInvalidSdk(jdkName, IdeaXml.JAVA_SDK_TYPE);
266             unknownJdks.add(jdkName);
267           }
268         }
269         rearrangeOrderEntryOfType(rootModel, JdkOrderEntry.class);
270       }
271       else if (path.startsWith(EclipseXml.USER_LIBRARY)) {
272         addNamedLibrary(rootModel, unknownLibraries, exported, getPresentableName(path), LibraryTablesRegistrar.PROJECT_LEVEL);
273       }
274       else if (path.startsWith(EclipseXml.JUNIT_CONTAINER)) {
275         final String junitName = IdeaXml.JUNIT + getPresentableName(path);
276         final Library library = rootModel.getModuleLibraryTable().getModifiableModel().createLibrary(junitName);
277         final Library.ModifiableModel modifiableModel = library.getModifiableModel();
278         modifiableModel.addRoot(getJunitClsUrl(junitName.contains("4")), OrderRootType.CLASSES);
279         modifiableModel.commit();
280       } else {
281         EclipseModuleManager.getInstance(rootModel.getModule()).registerUnknownCons(path);
282         addNamedLibrary(rootModel, new ArrayList<String>(), exported, path, LibraryTablesRegistrar.APPLICATION_LEVEL);
283       }
284     }
285     else {
286       throw new ConversionException("Unknown classpathentry/@kind: " + kind);
287     }
288   }
289
290   private static void rearrangeOrderEntryOfType(ModifiableRootModel rootModel, Class<? extends OrderEntry> orderEntryClass) {
291     OrderEntry[] orderEntries = rootModel.getOrderEntries();
292     int moduleSourcesIdx = 0;
293     for (OrderEntry orderEntry : orderEntries) {
294       if (orderEntryClass.isAssignableFrom(orderEntry.getClass())) {
295         break;
296       }
297       moduleSourcesIdx++;
298     }
299     orderEntries = ArrayUtil.append(orderEntries, orderEntries[moduleSourcesIdx]);
300     rootModel.rearrangeOrderEntries(ArrayUtil.remove(orderEntries, moduleSourcesIdx));
301   }
302
303   public static void setupOutput(ModifiableRootModel rootModel, final String path) {
304     final CompilerModuleExtension compilerModuleExtension = rootModel.getModuleExtension(CompilerModuleExtension.class);
305     compilerModuleExtension.setCompilerOutputPath(VfsUtil.pathToUrl(path));
306     compilerModuleExtension.inheritCompilerOutputPath(false);
307   }
308
309   private static void setLibraryEntryExported(ModifiableRootModel rootModel, boolean exported, Library library) {
310     for (OrderEntry orderEntry : rootModel.getOrderEntries()) {
311       if (orderEntry instanceof LibraryOrderEntry &&
312           ((LibraryOrderEntry)orderEntry).isModuleLevel() &&
313           Comparing.equal(((LibraryOrderEntry)orderEntry).getLibrary(), library)) {
314         ((LibraryOrderEntry)orderEntry).setExported(exported);
315         break;
316       }
317     }
318   }
319
320   private void addNamedLibrary(final ModifiableRootModel rootModel,
321                                final Collection<String> unknownLibraries,
322                                final boolean exported,
323                                final String name,
324                                final String notFoundLibraryLevel) {
325     Library lib = findLibraryByName(myProject, name);
326     if (lib != null) {
327       rootModel.addLibraryEntry(lib).setExported(exported);
328     }
329     else {
330       unknownLibraries.add(name);
331       rootModel.addInvalidLibrary(name, notFoundLibraryLevel).setExported(exported);
332     }
333   }
334
335   public static Library findLibraryByName(Project project, String name) {
336     final LibraryTablesRegistrar tablesRegistrar = LibraryTablesRegistrar.getInstance();
337     Library lib = tablesRegistrar.getLibraryTable().getLibraryByName(name);
338     if (lib == null) {
339       lib = tablesRegistrar.getLibraryTable(project).getLibraryByName(name);
340     }
341     if (lib == null) {
342       for (LibraryTable table : tablesRegistrar.getCustomLibraryTables()) {
343         lib = table.getLibraryByName(name);
344         if (lib != null) {
345           break;
346         }
347       }
348     }
349     return lib;
350   }
351
352   @NotNull
353   private static String getPresentableName(@NotNull String path) {
354     final String pathComponent = getLastPathComponent(path);
355     return pathComponent != null ? pathComponent : path;
356   }
357
358   @Nullable
359   public static String getLastPathComponent(final String path) {
360     final int idx = path.lastIndexOf('/');
361     return idx < 0 || idx == path.length() - 1 ? null : path.substring(idx + 1);
362   }
363
364   private ContentEntry getContentEntry() {
365     return myContentEntry;
366   }
367
368   private static String getVariableRelatedPath(String var, String path) {
369     return var == null ? null : ("$" + var + "$" + (path == null ? "" : ("/" + path)));
370   }
371
372   private String getUrl(final String path, ModifiableRootModel model) {
373     String url = null;
374     if (path.startsWith("/")) {
375       final String relativePath = new File(myRootPath).getParent() + "/" + path;
376       final File file = new File(relativePath);
377       if (file.exists()) {
378         url = VfsUtil.pathToUrl(relativePath);
379       } else if (new File(path).exists()) {
380         url = VfsUtil.pathToUrl(path);
381       }
382       else {
383         final String rootPath = getRootPath(path);
384         final String relativeToRootPath = getRelativeToRootPath(path);
385
386         final Module otherModule = ModuleManager.getInstance(myProject).findModuleByName(rootPath);
387         if (otherModule != null && otherModule != model.getModule()) {
388           url = relativeToOtherModule(otherModule, relativeToRootPath);
389         }
390         else if (myCurrentRoots != null) {
391           url = relativeToContentRoots(myCurrentRoots, rootPath, relativeToRootPath);
392         }
393       }
394     }
395     if (url == null) {
396       final String absPath = myRootPath + "/" + path;
397       if (new File(absPath).exists()) {
398         url = VfsUtil.pathToUrl(absPath);
399       }
400       else {
401         url = VfsUtil.pathToUrl(path);
402       }
403     }
404     final VirtualFile localFile = VirtualFileManager.getInstance().findFileByUrl(url);
405     if (localFile != null) {
406       final VirtualFile jarFile = JarFileSystem.getInstance().getJarRootForLocalFile(localFile);
407       if (jarFile != null) {
408         url = jarFile.getUrl();
409       }
410     }
411     return url;
412   }
413
414   static String getJunitClsUrl(final boolean version4) {
415     String url = version4 ? JavaSdkUtil.getJunit4JarPath() : JavaSdkUtil.getJunit3JarPath();
416     final VirtualFile localFile = VirtualFileManager.getInstance().findFileByUrl(VfsUtil.pathToUrl(url));
417     if (localFile != null) {
418       final VirtualFile jarFile = JarFileSystem.getInstance().getJarRootForLocalFile(localFile);
419       url = jarFile != null ? jarFile.getUrl() : localFile.getUrl();
420     }
421     return url;
422   }
423
424 }