2 * Copyright 2000-2015 JetBrains s.r.o.
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
8 * http://www.apache.org/licenses/LICENSE-2.0
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.
16 package com.intellij.openapi.externalSystem.service.project.manage;
18 import com.intellij.compiler.CompilerConfiguration;
19 import com.intellij.ide.util.projectWizard.ModuleBuilder;
20 import com.intellij.openapi.application.ApplicationManager;
21 import com.intellij.openapi.diagnostic.Logger;
22 import com.intellij.openapi.externalSystem.model.DataNode;
23 import com.intellij.openapi.externalSystem.model.ProjectKeys;
24 import com.intellij.openapi.externalSystem.model.ProjectSystemId;
25 import com.intellij.openapi.externalSystem.model.project.ExternalSystemSourceType;
26 import com.intellij.openapi.externalSystem.model.project.ModuleData;
27 import com.intellij.openapi.externalSystem.model.project.OrderAware;
28 import com.intellij.openapi.externalSystem.model.project.ProjectData;
29 import com.intellij.openapi.externalSystem.service.project.IdeModifiableModelsProvider;
30 import com.intellij.openapi.externalSystem.util.ExternalSystemApiUtil;
31 import com.intellij.openapi.externalSystem.util.ExternalSystemBundle;
32 import com.intellij.openapi.externalSystem.util.ExternalSystemConstants;
33 import com.intellij.openapi.externalSystem.util.ExternalSystemUiUtil;
34 import com.intellij.openapi.module.ModifiableModuleModel;
35 import com.intellij.openapi.module.Module;
36 import com.intellij.openapi.project.Project;
37 import com.intellij.openapi.roots.*;
38 import com.intellij.openapi.ui.DialogWrapper;
39 import com.intellij.openapi.util.Computable;
40 import com.intellij.openapi.util.Key;
41 import com.intellij.openapi.util.Pair;
42 import com.intellij.openapi.util.io.FileUtil;
43 import com.intellij.openapi.vfs.VfsUtilCore;
44 import com.intellij.pom.java.LanguageLevel;
45 import com.intellij.ui.CheckBoxList;
46 import com.intellij.ui.IdeBorderFactory;
47 import com.intellij.ui.components.JBScrollPane;
48 import com.intellij.util.ArrayUtil;
49 import com.intellij.util.Consumer;
50 import com.intellij.util.Function;
51 import com.intellij.util.SmartList;
52 import com.intellij.util.containers.ContainerUtil;
53 import com.intellij.util.containers.ContainerUtilRt;
54 import org.jetbrains.annotations.NotNull;
55 import org.jetbrains.annotations.Nullable;
60 import java.util.List;
63 * @author Vladislav.Soroka
66 public abstract class AbstractModuleDataService<E extends ModuleData> extends AbstractProjectDataService<E, Module> {
68 public static final Key<ModuleData> MODULE_DATA_KEY = Key.create("MODULE_DATA_KEY");
69 public static final Key<Module> MODULE_KEY = Key.create("LINKED_MODULE");
70 public static final Key<Map<OrderEntry, OrderAware>> ORDERED_DATA_MAP_KEY = Key.create("ORDER_ENTRY_DATA_MAP");
71 public static final Key<Set<String>> ORPHAN_MODULE_FILES = Key.create("ORPHAN_FILES");
73 private static final Logger LOG = Logger.getInstance(AbstractModuleDataService.class);
76 public void importData(@NotNull final Collection<DataNode<E>> toImport,
77 @Nullable ProjectData projectData,
78 @NotNull final Project project,
79 @NotNull IdeModifiableModelsProvider modelsProvider) {
80 if (toImport.isEmpty()) {
84 final Collection<DataNode<E>> toCreate = filterExistingModules(toImport, modelsProvider, project);
85 if (!toCreate.isEmpty()) {
86 createModules(toCreate, modelsProvider, project);
89 for (DataNode<E> node : toImport) {
90 Module module = node.getUserData(MODULE_KEY);
92 setModuleOptions(module, node);
93 ModifiableRootModel modifiableRootModel = modelsProvider.getModifiableRootModel(module);
94 syncPaths(module, modifiableRootModel, node.getData());
95 setLanguageLevel(modifiableRootModel, node.getData());
99 final boolean isOneToOneMapping = projectData != null && ExternalSystemApiUtil.isOneToOneMapping(project, projectData);
100 for (DataNode<E> node : toImport) {
101 Module module = node.getUserData(MODULE_KEY);
102 if (module != null) {
103 final String[] groupPath;
104 if (isOneToOneMapping || projectData == null) {
105 groupPath = node.getData().getIdeModuleGroup();
108 final String externalProjectGroup = projectData.getInternalName() + " modules";
109 groupPath = node.getData().getIdeModuleGroup() == null
110 ? new String[]{externalProjectGroup}
111 : ArrayUtil.prepend(externalProjectGroup, node.getData().getIdeModuleGroup());
113 final ModifiableModuleModel modifiableModel = modelsProvider.getModifiableModuleModel();
114 modifiableModel.setModuleGroupPath(module, groupPath);
119 private void createModules(@NotNull Collection<DataNode<E>> toCreate,
120 @NotNull IdeModifiableModelsProvider modelsProvider,
121 @NotNull Project project) {
122 for (final DataNode<E> module : toCreate) {
123 ModuleData data = module.getData();
124 final Module created = modelsProvider.newModule(data.getModuleFilePath(), data.getModuleTypeId());
125 module.putUserData(MODULE_KEY, created);
126 String productionModuleId = data.getProductionModuleId();
127 if (productionModuleId != null) {
128 modelsProvider.setTestModuleProperties(created, productionModuleId);
130 Set<String> orphanFiles = project.getUserData(ORPHAN_MODULE_FILES);
131 if (orphanFiles != null) {
132 orphanFiles.remove(created.getModuleFilePath());
135 // Ensure that the dependencies are clear (used to be not clear when manually removing the module and importing it via gradle)
136 final ModifiableRootModel modifiableRootModel = modelsProvider.getModifiableRootModel(created);
137 modifiableRootModel.inheritSdk();
139 RootPolicy<Object> visitor = new RootPolicy<Object>() {
141 public Object visitLibraryOrderEntry(LibraryOrderEntry libraryOrderEntry, Object value) {
142 modifiableRootModel.removeOrderEntry(libraryOrderEntry);
147 public Object visitModuleOrderEntry(ModuleOrderEntry moduleOrderEntry, Object value) {
148 modifiableRootModel.removeOrderEntry(moduleOrderEntry);
153 for (OrderEntry orderEntry : modifiableRootModel.getOrderEntries()) {
154 orderEntry.accept(visitor, null);
160 private Collection<DataNode<E>> filterExistingModules(@NotNull Collection<DataNode<E>> modules,
161 @NotNull IdeModifiableModelsProvider modelsProvider,
162 @NotNull Project project) {
163 Collection<DataNode<E>> result = ContainerUtilRt.newArrayList();
164 for (DataNode<E> node : modules) {
165 ModuleData moduleData = node.getData();
166 Module module = modelsProvider.findIdeModule(moduleData.getInternalName());
167 if (module == null) {
171 if (!FileUtil.pathsEqual(module.getModuleFilePath(), moduleData.getModuleFilePath())) {
172 modelsProvider.getModifiableModuleModel().disposeModule(module);
174 Set<String> orphanFiles = project.getUserData(ORPHAN_MODULE_FILES);
175 if (orphanFiles == null) {
176 project.putUserData(ORPHAN_MODULE_FILES, orphanFiles = ContainerUtil.newHashSet());
178 orphanFiles.add(module.getModuleFilePath());
181 node.putUserData(MODULE_KEY, module);
188 private static void syncPaths(@NotNull Module module, @NotNull ModifiableRootModel modifiableModel, @NotNull ModuleData data) {
189 CompilerModuleExtension extension = modifiableModel.getModuleExtension(CompilerModuleExtension.class);
190 if (extension == null) {
191 //modifiableModel.dispose();
192 LOG.warn(String.format("Can't sync paths for module '%s'. Reason: no compiler extension is found for it", module.getName()));
195 String compileOutputPath = data.getCompileOutputPath(ExternalSystemSourceType.SOURCE);
196 extension.setCompilerOutputPath(compileOutputPath != null ? VfsUtilCore.pathToUrl(compileOutputPath) : null);
198 String testCompileOutputPath = data.getCompileOutputPath(ExternalSystemSourceType.TEST);
199 extension.setCompilerOutputPathForTests(testCompileOutputPath != null ? VfsUtilCore.pathToUrl(testCompileOutputPath) : null);
201 extension.inheritCompilerOutputPath(data.isInheritProjectCompileOutputPath());
205 public void removeData(@NotNull final Computable<Collection<Module>> toRemoveComputable,
206 @NotNull final Collection<DataNode<E>> toIgnore,
207 @NotNull final ProjectData projectData,
208 @NotNull final Project project,
209 @NotNull final IdeModifiableModelsProvider modelsProvider) {
210 final Collection<Module> toRemove = toRemoveComputable.compute();
211 final List<Module> modules = new SmartList<Module>(toRemove);
212 for (DataNode<E> moduleDataNode : toIgnore) {
213 final Module module = modelsProvider.findIdeModule(moduleDataNode.getData());
214 ContainerUtil.addIfNotNull(modules, module);
217 if (modules.isEmpty()) {
221 ContainerUtil.removeDuplicates(modules);
223 for (Module module : modules) {
224 if (module.isDisposed()) continue;
225 unlinkModuleFromExternalSystem(module);
228 ruleOrphanModules(modules, project, projectData.getOwner(), new Consumer<List<Module>>() {
230 public void consume(final List<Module> modules) {
231 for (Module module : modules) {
232 if (module.isDisposed()) continue;
233 String path = module.getModuleFilePath();
234 final ModifiableModuleModel moduleModel = modelsProvider.getModifiableModuleModel();
235 moduleModel.disposeModule(module);
236 ModuleBuilder.deleteModuleFile(path);
243 * There is a possible case that an external module has been un-linked from ide project. There are two ways to process
244 * ide modules which correspond to that external project:
247 * <li>Remove them from ide project as well;</li>
248 * <li>Keep them at ide project as well;</li>
251 * This method handles that situation, i.e. it asks a user what should be done and acts accordingly.
253 * @param orphanModules modules which correspond to the un-linked external project
254 * @param project current ide project
255 * @param externalSystemId id of the external system which project has been un-linked from ide project
257 private static void ruleOrphanModules(@NotNull final List<Module> orphanModules,
258 @NotNull final Project project,
259 @NotNull final ProjectSystemId externalSystemId,
260 @NotNull final Consumer<List<Module>> result) {
261 ExternalSystemApiUtil.executeOnEdt(true, new Runnable() {
264 List<Module> toRemove = ContainerUtil.newSmartList();
265 if (ApplicationManager.getApplication().isHeadlessEnvironment()) {
266 toRemove.addAll(orphanModules);
269 final JPanel content = new JPanel(new GridBagLayout());
270 content.add(new JLabel(ExternalSystemBundle.message("orphan.modules.text", externalSystemId.getReadableName())),
271 ExternalSystemUiUtil.getFillLineConstraints(0));
273 final CheckBoxList<Module> orphanModulesList = new CheckBoxList<Module>();
274 orphanModulesList.setSelectionMode(ListSelectionModel.MULTIPLE_INTERVAL_SELECTION);
275 orphanModulesList.setItems(orphanModules, new Function<Module, String>() {
277 public String fun(Module module) {
278 return module.getName();
281 for (Module module : orphanModules) {
282 orphanModulesList.setItemSelected(module, true);
284 orphanModulesList.setBorder(IdeBorderFactory.createEmptyBorder(8));
285 content.add(orphanModulesList, ExternalSystemUiUtil.getFillLineConstraints(0));
286 content.setBorder(IdeBorderFactory.createEmptyBorder(0, 0, 8, 0));
288 DialogWrapper dialog = new DialogWrapper(project) {
290 setTitle(ExternalSystemBundle.message("import.title", externalSystemId.getReadableName()));
296 protected JComponent createCenterPanel() {
297 return new JBScrollPane(content);
301 protected Action[] createActions() {
302 return new Action[]{getOKAction()};
308 for (int i = 0; i < orphanModules.size(); i++) {
309 Module module = orphanModules.get(i);
310 if (orphanModulesList.isItemSelected(i)) {
311 toRemove.add(module);
315 result.consume(toRemove);
320 public static void unlinkModuleFromExternalSystem(@NotNull Module module) {
321 module.clearOption(ExternalSystemConstants.EXTERNAL_SYSTEM_ID_KEY);
322 module.clearOption(ExternalSystemConstants.LINKED_PROJECT_ID_KEY);
323 module.clearOption(ExternalSystemConstants.LINKED_PROJECT_PATH_KEY);
324 module.clearOption(ExternalSystemConstants.ROOT_PROJECT_PATH_KEY);
325 module.clearOption(ExternalSystemConstants.EXTERNAL_SYSTEM_MODULE_GROUP_KEY);
326 module.clearOption(ExternalSystemConstants.EXTERNAL_SYSTEM_MODULE_VERSION_KEY);
329 protected void setModuleOptions(Module module, DataNode<E> moduleDataNode) {
330 ModuleData moduleData = moduleDataNode.getData();
331 module.putUserData(MODULE_DATA_KEY, moduleData);
333 module.setOption(ExternalSystemConstants.EXTERNAL_SYSTEM_ID_KEY, moduleData.getOwner().toString());
334 module.setOption(ExternalSystemConstants.LINKED_PROJECT_ID_KEY, moduleData.getId());
335 module.setOption(ExternalSystemConstants.LINKED_PROJECT_PATH_KEY, moduleData.getLinkedExternalProjectPath());
336 final ProjectData projectData = moduleDataNode.getData(ProjectKeys.PROJECT);
337 module.setOption(ExternalSystemConstants.ROOT_PROJECT_PATH_KEY, projectData != null ? projectData.getLinkedExternalProjectPath() : "");
339 if (moduleData.getGroup() != null) {
340 module.setOption(ExternalSystemConstants.EXTERNAL_SYSTEM_MODULE_GROUP_KEY, moduleData.getGroup());
342 if (moduleData.getVersion() != null) {
343 module.setOption(ExternalSystemConstants.EXTERNAL_SYSTEM_MODULE_VERSION_KEY, moduleData.getVersion());
346 // clear maven option
347 module.clearOption("org.jetbrains.idea.maven.project.MavenProjectsManager.isMavenModule");
351 public void postProcess(@NotNull Collection<DataNode<E>> toImport,
352 @Nullable ProjectData projectData,
353 @NotNull Project project,
354 @NotNull IdeModifiableModelsProvider modelsProvider) {
355 for (DataNode<E> moduleDataNode : toImport) {
356 final Module module = moduleDataNode.getUserData(MODULE_KEY);
357 if (module == null) continue;
358 final Map<OrderEntry, OrderAware> orderAwareMap = moduleDataNode.getUserData(ORDERED_DATA_MAP_KEY);
359 if (orderAwareMap != null) {
360 rearrangeOrderEntries(orderAwareMap, modelsProvider.getModifiableRootModel(module));
362 setBytecodeTargetLevel(project, module, moduleDataNode.getData());
367 public void onSuccessImport(@NotNull Project project) {
368 final Set<String> orphanFiles = project.getUserData(ORPHAN_MODULE_FILES);
369 if (orphanFiles != null && !orphanFiles.isEmpty()) {
370 ExternalSystemApiUtil.executeOnEdt(false, new Runnable() {
373 for (String orphanFile : orphanFiles) {
374 ModuleBuilder.deleteModuleFile(orphanFile);
378 project.putUserData(ORPHAN_MODULE_FILES, null);
382 protected void rearrangeOrderEntries(@NotNull Map<OrderEntry, OrderAware> orderEntryDataMap,
383 @NotNull ModifiableRootModel modifiableRootModel) {
384 final OrderEntry[] orderEntries = modifiableRootModel.getOrderEntries();
385 final int length = orderEntries.length;
386 final OrderEntry[] newOrder = new OrderEntry[length];
387 final PriorityQueue<Pair<OrderEntry, OrderAware>> priorityQueue = new PriorityQueue<Pair<OrderEntry, OrderAware>>(
388 11, new Comparator<Pair<OrderEntry, OrderAware>>() {
390 public int compare(Pair<OrderEntry, OrderAware> o1, Pair<OrderEntry, OrderAware> o2) {
391 int order1 = o1.second.getOrder();
392 int order2 = o2.second.getOrder();
393 return order1 != order2 ? order1 < order2 ? -1 : 1 : 0;
398 for (int i = 0; i < length; i++) {
399 OrderEntry orderEntry = orderEntries[i];
400 final OrderAware orderAware = orderEntryDataMap.get(orderEntry);
401 if (orderAware == null) {
402 newOrder[i] = orderEntry;
406 priorityQueue.add(Pair.create(orderEntry, orderAware));
410 Pair<OrderEntry, OrderAware> pair;
411 while ((pair = priorityQueue.poll()) != null) {
412 final OrderEntry orderEntry = pair.first;
413 final OrderAware orderAware = pair.second;
414 final int order = orderAware.getOrder() != -1 ? orderAware.getOrder() : length - 1;
415 final int newPlace = findNewPlace(newOrder, order - shift);
416 assert newPlace != -1;
417 newOrder[newPlace] = orderEntry;
420 if (LOG.isDebugEnabled()) {
421 final boolean changed = !ArrayUtil.equals(orderEntries, newOrder, new Comparator<OrderEntry>() {
423 public int compare(OrderEntry o1, OrderEntry o2) {
424 return o1.compareTo(o2);
427 LOG.debug(String.format("rearrange status (%s): %s", modifiableRootModel.getModule(), changed ? "modified" : "not modified"));
429 modifiableRootModel.rearrangeOrderEntries(newOrder);
432 private static int findNewPlace(OrderEntry[] newOrder, int newIndex) {
434 while (idx < 0 || (idx < newOrder.length && newOrder[idx] != null)) {
437 if (idx >= newOrder.length) {
439 while (idx >= 0 && (idx >= newOrder.length || newOrder[idx] != null)) {
443 return idx == -1 ? -1 : idx;
446 private void setLanguageLevel(@NotNull ModifiableRootModel modifiableRootModel, E data) {
447 LanguageLevel level = LanguageLevel.parse(data.getSourceCompatibility());
450 modifiableRootModel.getModuleExtension(LanguageLevelModuleExtension.class).setLanguageLevel(level);
452 catch (IllegalArgumentException e) {
458 private void setBytecodeTargetLevel(@NotNull Project project, @NotNull Module module, @NotNull E data) {
459 String targetLevel = data.getTargetCompatibility();
460 if (targetLevel != null) {
461 CompilerConfiguration configuration = CompilerConfiguration.getInstance(project);
462 configuration.setBytecodeTargetLevel(module, targetLevel);