blob: 4541cf57f7a773a89102b2ad9d6c8dee3e185ca8 [file] [log] [blame]
/*
* Copyright 2000-2009 JetBrains s.r.o.
*
* 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.
*/
/*
* User: anna
* Date: 18-Dec-2009
*/
package org.jetbrains.idea.eclipse.conversion;
import com.intellij.openapi.components.PathMacroManager;
import com.intellij.openapi.diagnostic.Logger;
import com.intellij.openapi.module.Module;
import com.intellij.openapi.module.ModuleManager;
import com.intellij.openapi.module.ModuleUtilCore;
import com.intellij.openapi.project.Project;
import com.intellij.openapi.projectRoots.ProjectJdkTable;
import com.intellij.openapi.projectRoots.Sdk;
import com.intellij.openapi.roots.*;
import com.intellij.openapi.roots.libraries.Library;
import com.intellij.openapi.roots.libraries.LibraryTablesRegistrar;
import com.intellij.openapi.util.Comparing;
import com.intellij.openapi.util.InvalidDataException;
import com.intellij.openapi.util.WriteExternalException;
import com.intellij.openapi.util.io.FileUtil;
import com.intellij.openapi.util.text.StringUtil;
import com.intellij.openapi.vfs.JarFileSystem;
import com.intellij.openapi.vfs.VfsUtilCore;
import com.intellij.openapi.vfs.VirtualFile;
import com.intellij.openapi.vfs.VirtualFileManager;
import com.intellij.pom.java.LanguageLevel;
import org.jdom.Element;
import org.jdom.JDOMException;
import org.jetbrains.annotations.NonNls;
import org.jetbrains.annotations.Nullable;
import org.jetbrains.idea.eclipse.IdeaXml;
import org.jetbrains.idea.eclipse.config.CachedXmlDocumentSet;
import org.jetbrains.idea.eclipse.config.EclipseModuleManagerImpl;
import java.io.File;
import java.io.IOException;
import java.util.*;
import static org.jetbrains.idea.eclipse.conversion.EPathUtil.areUrlsPointTheSame;
/**
* Read/write .eml
*/
public class IdeaSpecificSettings extends AbstractIdeaSpecificSettings<ModifiableRootModel, ContentEntry, Sdk> {
@NonNls private static final String RELATIVE_MODULE_SRC = "relative-module-src";
@NonNls private static final String RELATIVE_MODULE_CLS = "relative-module-cls";
@NonNls private static final String RELATIVE_MODULE_JAVADOC = "relative-module-javadoc";
@NonNls private static final String PROJECT_RELATED = "project-related";
@NonNls private static final String SRCROOT_ATTR = "srcroot";
@NonNls private static final String SRCROOT_BIND_ATTR = "bind";
private static final Logger LOG = Logger.getInstance("#" + IdeaSpecificSettings.class.getName());
@NonNls private static final String JAVADOCROOT_ATTR = "javadocroot_attr";
public static final String INHERIT_JDK = "inheritJdk";
private IdeaSpecificSettings() {
}
public static void readIDEASpecific(ModifiableRootModel model, CachedXmlDocumentSet documentSet, String eml) throws InvalidDataException, IOException, JDOMException {
new IdeaSpecificSettings().readIDEASpecific(documentSet.read(eml, false).getRootElement(), model, null, new HashMap<String, String>());
}
@Override
protected void readLibraryLevels(Element root, Map<String, String> levels) {
}
@Override
protected ContentEntry[] getEntries(ModifiableRootModel model) {
return model.getContentEntries();
}
@Override
protected ContentEntry createContentEntry(ModifiableRootModel model, final String url) {
return model.addContentEntry(url);
}
@Override
protected void setupLibraryRoots(Element root, ModifiableRootModel model) {
for (Object o : root.getChildren("lib")) {
Element libElement = (Element)o;
final String libName = libElement.getAttributeValue("name");
Library libraryByName = model.getModuleLibraryTable().getLibraryByName(libName);
if (libraryByName != null) {
appendLibraryScope(model, libElement, libraryByName);
final Library.ModifiableModel modifiableModel = libraryByName.getModifiableModel();
replaceCollapsedByEclipseSourceRoots(libElement, modifiableModel);
for (Object r : libElement.getChildren(JAVADOCROOT_ATTR)) {
final String url = ((Element)r).getAttributeValue("url");
modifiableModel.addRoot(url, JavadocOrderRootType.getInstance());
}
replaceModuleRelatedRoots(model.getProject(), modifiableModel, libElement, OrderRootType.SOURCES, RELATIVE_MODULE_SRC);
replaceModuleRelatedRoots(model.getProject(), modifiableModel, libElement, OrderRootType.CLASSES, RELATIVE_MODULE_CLS);
replaceModuleRelatedRoots(model.getProject(), modifiableModel, libElement, JavadocOrderRootType.getInstance(), RELATIVE_MODULE_JAVADOC);
modifiableModel.commit();
} else {
final Library library = EclipseClasspathReader.findLibraryByName(model.getProject(), libName);
if (library != null) {
appendLibraryScope(model, libElement, library);
}
}
}
}
@Override
protected void setupJdk(Element root, ModifiableRootModel model, @Nullable Sdk sdk) {
final String inheritJdk = root.getAttributeValue(INHERIT_JDK);
if (inheritJdk != null && Boolean.parseBoolean(inheritJdk)) {
model.inheritSdk();
} else {
final String jdkName = root.getAttributeValue("jdk");
if (jdkName != null) {
final Sdk jdkByName = ProjectJdkTable.getInstance().findJdk(jdkName);
if (jdkByName != null) {
model.setSdk(jdkByName);
}
}
}
}
@Override
protected void setupCompilerOutputs(Element root, ModifiableRootModel model) {
final CompilerModuleExtension compilerModuleExtension = model.getModuleExtension(CompilerModuleExtension.class);
final Element testOutputElement = root.getChild(IdeaXml.OUTPUT_TEST_TAG);
if (testOutputElement != null) {
compilerModuleExtension.setCompilerOutputPathForTests(testOutputElement.getAttributeValue(IdeaXml.URL_ATTR));
}
final String inheritedOutput = root.getAttributeValue(IdeaXml.INHERIT_COMPILER_OUTPUT_ATTR);
if (inheritedOutput != null && Boolean.valueOf(inheritedOutput).booleanValue()) {
compilerModuleExtension.inheritCompilerOutputPath(true);
}
compilerModuleExtension.setExcludeOutput(root.getChild(IdeaXml.EXCLUDE_OUTPUT_TAG) != null);
}
@Override
protected void readLanguageLevel(Element root, ModifiableRootModel model) throws InvalidDataException {
model.getModuleExtension(LanguageLevelModuleExtension.class).readExternal(root);
}
@Override
protected void expandElement(Element root, ModifiableRootModel model) {
PathMacroManager.getInstance(model.getModule()).expandPaths(root);
}
@Override
protected void overrideModulesScopes(Element root, ModifiableRootModel model) {
for (Object o : root.getChildren("module")) {
final String moduleName = ((Element)o).getAttributeValue("name");
final String scope = ((Element)o).getAttributeValue("scope");
if (scope != null) {
for (OrderEntry entry : model.getOrderEntries()) {
if (entry instanceof ModuleOrderEntry && Comparing.strEqual(((ModuleOrderEntry)entry).getModuleName(), moduleName)) {
((ModuleOrderEntry)entry).setScope(DependencyScope.valueOf(scope));
break;
}
}
}
}
}
private static void appendLibraryScope(ModifiableRootModel model, Element libElement, Library libraryByName) {
final LibraryOrderEntry libraryOrderEntry = model.findLibraryOrderEntry(libraryByName);
if (libraryOrderEntry != null) {
final String scopeAttribute = libElement.getAttributeValue("scope");
libraryOrderEntry.setScope(scopeAttribute == null ? DependencyScope.COMPILE : DependencyScope.valueOf(scopeAttribute));
}
}
/**
* Eclipse detect sources inside zip automatically while IDEA doesn't.
* So .eml contains expanded roots which should replace zip root read from .classpath
*/
private static void replaceCollapsedByEclipseSourceRoots(Element libElement, Library.ModifiableModel modifiableModel) {
String[] srcUrlsFromClasspath = modifiableModel.getUrls(OrderRootType.SOURCES);
LOG.assertTrue(srcUrlsFromClasspath.length <= 1);
final String eclipseUrl = srcUrlsFromClasspath.length > 0 ? srcUrlsFromClasspath[0] : null;
for (Object r : libElement.getChildren(SRCROOT_ATTR)) {
final String url = ((Element)r).getAttributeValue("url");
final String bindAttr = ((Element)r).getAttributeValue(SRCROOT_BIND_ATTR);
boolean notBind = bindAttr != null && !Boolean.parseBoolean(bindAttr);
if (notBind) {
modifiableModel.addRoot(url, OrderRootType.SOURCES);
}
else if (eclipseUrl != null && areUrlsPointTheSame(url, eclipseUrl) && !Comparing.strEqual(url, eclipseUrl)) { //todo lost already configured additional src roots
modifiableModel.addRoot(url, OrderRootType.SOURCES);
if (srcUrlsFromClasspath != null && srcUrlsFromClasspath.length == 1) { //remove compound root
modifiableModel.removeRoot(eclipseUrl, OrderRootType.SOURCES);
srcUrlsFromClasspath = null;
}
}
}
}
@Override
public void readContentEntry(Element root, ContentEntry entry, ModifiableRootModel model) {
final SourceFolder[] folders = entry.getSourceFolders();
final String[] sourceFoldersUrls = new String[folders.length];
for (int i = 0; i < folders.length; i++) {
final SourceFolder folder = folders[i];
sourceFoldersUrls[i] = folder.getUrl();
entry.removeSourceFolder(folder);
}
final boolean[] testFolders = new boolean[sourceFoldersUrls.length];
for (Object o : root.getChildren(IdeaXml.TEST_FOLDER_TAG)) {
final String url = ((Element)o).getAttributeValue(IdeaXml.URL_ATTR);
for (int i = 0; i < sourceFoldersUrls.length; i++) {
if (Comparing.strEqual(sourceFoldersUrls[i], url)) {
testFolders[i] = true;
break;
}
}
}
for (int i = 0; i < sourceFoldersUrls.length; i++) {
entry.addSourceFolder(sourceFoldersUrls[i], testFolders[i]);
}
for (Object o : root.getChildren(IdeaXml.PACKAGE_PREFIX_TAG)) {
Element ppElement = (Element)o;
final String prefix = ppElement.getAttributeValue(IdeaXml.PACKAGE_PREFIX_VALUE_ATTR);
final String url = ppElement.getAttributeValue(IdeaXml.URL_ATTR);
for (SourceFolder folder : entry.getSourceFolders()) {
if (Comparing.strEqual(folder.getUrl(), url)) {
folder.setPackagePrefix(prefix);
break;
}
}
}
final String url = entry.getUrl();
for (Object o : root.getChildren(IdeaXml.EXCLUDE_FOLDER_TAG)) {
final String excludeUrl = ((Element)o).getAttributeValue(IdeaXml.URL_ATTR);
if (FileUtil.isAncestor(new File(url), new File(excludeUrl), false)) { //check if it is excluded manually
entry.addExcludeFolder(excludeUrl);
}
}
}
public static boolean writeIDEASpecificClasspath(final Element root, ModuleRootModel model) throws WriteExternalException {
boolean isModified = false;
final CompilerModuleExtension compilerModuleExtension = model.getModuleExtension(CompilerModuleExtension.class);
if (compilerModuleExtension.getCompilerOutputUrlForTests() != null) {
final Element pathElement = new Element(IdeaXml.OUTPUT_TEST_TAG);
pathElement.setAttribute(IdeaXml.URL_ATTR, compilerModuleExtension.getCompilerOutputUrlForTests());
root.addContent(pathElement);
isModified = true;
}
if (compilerModuleExtension.isCompilerOutputPathInherited()) {
root.setAttribute(IdeaXml.INHERIT_COMPILER_OUTPUT_ATTR, String.valueOf(true));
isModified = true;
}
if (compilerModuleExtension.isExcludeOutput()) {
root.addContent(new Element(IdeaXml.EXCLUDE_OUTPUT_TAG));
isModified = true;
}
final LanguageLevelModuleExtension languageLevelModuleExtension = model.getModuleExtension(LanguageLevelModuleExtension.class);
final LanguageLevel languageLevel = languageLevelModuleExtension.getLanguageLevel();
if (languageLevel != null) {
languageLevelModuleExtension.writeExternal(root);
isModified = true;
}
for (ContentEntry entry : model.getContentEntries()) {
final Element contentEntryElement = new Element(IdeaXml.CONTENT_ENTRY_TAG);
contentEntryElement.setAttribute(IdeaXml.URL_ATTR, entry.getUrl());
root.addContent(contentEntryElement);
for (SourceFolder sourceFolder : entry.getSourceFolders()) {
if (sourceFolder.isTestSource()) {
Element element = new Element(IdeaXml.TEST_FOLDER_TAG);
contentEntryElement.addContent(element);
element.setAttribute(IdeaXml.URL_ATTR, sourceFolder.getUrl());
isModified = true;
}
final String packagePrefix = sourceFolder.getPackagePrefix();
if (!StringUtil.isEmptyOrSpaces(packagePrefix)) {
Element element = new Element(IdeaXml.PACKAGE_PREFIX_TAG);
contentEntryElement.addContent(element);
element.setAttribute(IdeaXml.URL_ATTR, sourceFolder.getUrl());
element.setAttribute(IdeaXml.PACKAGE_PREFIX_VALUE_ATTR, packagePrefix);
isModified = true;
}
}
final VirtualFile entryFile = entry.getFile();
for (ExcludeFolder excludeFolder : entry.getExcludeFolders()) {
final String exludeFolderUrl = excludeFolder.getUrl();
final VirtualFile excludeFile = excludeFolder.getFile();
if (entryFile == null || excludeFile == null || VfsUtilCore.isAncestor(entryFile, excludeFile, false)) {
Element element = new Element(IdeaXml.EXCLUDE_FOLDER_TAG);
contentEntryElement.addContent(element);
element.setAttribute(IdeaXml.URL_ATTR, exludeFolderUrl);
isModified = true;
}
}
}
final Map<String, String> libLevels = new LinkedHashMap<String, String>();
for (OrderEntry entry : model.getOrderEntries()) {
if (entry instanceof ModuleOrderEntry) {
final DependencyScope scope = ((ModuleOrderEntry)entry).getScope();
if (!scope.equals(DependencyScope.COMPILE)) {
Element element = new Element("module");
element.setAttribute("name", ((ModuleOrderEntry)entry).getModuleName());
element.setAttribute("scope", scope.name());
root.addContent(element);
isModified = true;
}
}
if (entry instanceof JdkOrderEntry) {
final Sdk jdk = ((JdkOrderEntry)entry).getJdk();
if (EclipseModuleManagerImpl.getInstance(entry.getOwnerModule()).getInvalidJdk() != null || jdk != null) {
if (entry instanceof InheritedJdkOrderEntry) {
root.setAttribute(INHERIT_JDK, "true");
} else {
root.setAttribute("jdk", ((JdkOrderEntry)entry).getJdkName());
if (jdk != null) {
root.setAttribute("jdk_type", jdk.getSdkType().getName());
}
}
isModified = true;
}
}
if (!(entry instanceof LibraryOrderEntry)) continue;
final Element element = new Element("lib");
element.setAttribute("name", entry.getPresentableName());
final LibraryOrderEntry libraryEntry = (LibraryOrderEntry)entry;
final DependencyScope scope = libraryEntry.getScope();
element.setAttribute("scope", scope.name());
if (libraryEntry.isModuleLevel()) {
final String[] urls = libraryEntry.getRootUrls(OrderRootType.SOURCES);
String eclipseUrl = null;
if (urls.length > 0) {
eclipseUrl = urls[0];
final int jarSeparatorIdx = urls[0].indexOf(JarFileSystem.JAR_SEPARATOR);
if (jarSeparatorIdx > -1) {
eclipseUrl = eclipseUrl.substring(0, jarSeparatorIdx);
}
}
for (String url : urls) {
Element srcElement = new Element(SRCROOT_ATTR);
srcElement.setAttribute("url", url);
if (!areUrlsPointTheSame(url, eclipseUrl)) {
srcElement.setAttribute(SRCROOT_BIND_ATTR, String.valueOf(false));
}
element.addContent(srcElement);
}
final String[] javadocUrls = libraryEntry.getRootUrls(JavadocOrderRootType.getInstance());
for (int i = 1; i < javadocUrls.length; i++) {
Element javadocElement = new Element(JAVADOCROOT_ATTR);
javadocElement.setAttribute("url", javadocUrls[i]);
element.addContent(javadocElement);
}
for (String srcUrl : libraryEntry.getRootUrls(OrderRootType.SOURCES)) {
appendModuleRelatedRoot(element, srcUrl, RELATIVE_MODULE_SRC, model);
}
for (String classesUrl : libraryEntry.getRootUrls(OrderRootType.CLASSES)) {
appendModuleRelatedRoot(element, classesUrl, RELATIVE_MODULE_CLS, model);
}
for (String javadocUrl : libraryEntry.getRootUrls(JavadocOrderRootType.getInstance())) {
appendModuleRelatedRoot(element, javadocUrl, RELATIVE_MODULE_JAVADOC, model);
}
if (!element.getChildren().isEmpty()) {
root.addContent(element);
isModified = true;
continue;
}
} else {
final String libraryLevel = libraryEntry.getLibraryLevel();
if (!LibraryTablesRegistrar.APPLICATION_LEVEL.equals(libraryLevel)) {
libLevels.put(libraryEntry.getLibraryName(), libraryLevel);
}
}
if (!scope.equals(DependencyScope.COMPILE)) {
root.addContent(element);
isModified = true;
}
}
if (!libLevels.isEmpty()) {
final Element libLevelsElement = new Element("levels");
for (String libName : libLevels.keySet()) {
final Element libElement = new Element("level");
libElement.setAttribute("name", libName);
libElement.setAttribute("value", libLevels.get(libName));
libLevelsElement.addContent(libElement);
}
root.addContent(libLevelsElement);
}
PathMacroManager.getInstance(model.getModule()).collapsePaths(root);
return isModified;
}
public static void replaceModuleRelatedRoots(final Project project,
final Library.ModifiableModel modifiableModel, final Element libElement,
final OrderRootType orderRootType, final String relativeModuleName) {
final List<String> urls = new ArrayList<String>(Arrays.asList(modifiableModel.getUrls(orderRootType)));
for (Object r : libElement.getChildren(relativeModuleName)) {
final String root = PathMacroManager.getInstance(project).expandPath(((Element)r).getAttributeValue(PROJECT_RELATED));
for (Iterator<String> iterator = urls.iterator(); iterator.hasNext();) {
final String url = iterator.next();
if (areUrlsPointTheSame(root, url)) {
iterator.remove();
modifiableModel.removeRoot(url, orderRootType);
modifiableModel.addRoot(root, orderRootType);
break;
}
}
}
}
public static boolean appendModuleRelatedRoot(Element element, String classesUrl, final String rootName, ModuleRootModel model) {
VirtualFile file = VirtualFileManager.getInstance().findFileByUrl(classesUrl);
final Project project = model.getModule().getProject();
if (file != null) {
if (file.getFileSystem() instanceof JarFileSystem) {
file = JarFileSystem.getInstance().getVirtualFileForJar(file);
assert file != null;
}
final Module module = ModuleUtilCore.findModuleForFile(file, project);
if (module != null) {
return appendRelatedToModule(element, classesUrl, rootName, file, module);
} else if (ProjectRootManager.getInstance(project).getFileIndex().isExcluded(file)) {
for (Module aModule : ModuleManager.getInstance(project).getModules()) {
if (appendRelatedToModule(element, classesUrl, rootName, file, aModule)) return true;
}
}
}
return false;
}
private static boolean appendRelatedToModule(Element element, String classesUrl, String rootName, VirtualFile file, Module module) {
final VirtualFile[] contentRoots = ModuleRootManager.getInstance(module).getContentRoots();
for (VirtualFile contentRoot : contentRoots) {
if (VfsUtilCore.isAncestor(contentRoot, file, false)) {
final Element clsElement = new Element(rootName);
clsElement.setAttribute(PROJECT_RELATED, PathMacroManager.getInstance(module.getProject()).collapsePath(classesUrl));
element.addContent(clsElement);
return true;
}
}
return false;
}
}