blob: 36e344ff0accae3849a1e78877cbebbaba9601fd [file] [log] [blame]
/*
* Copyright 2000-2014 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.
*/
package com.intellij.openapi.roots.impl.storage;
import com.intellij.openapi.application.ApplicationManager;
import com.intellij.openapi.components.PathMacroManager;
import com.intellij.openapi.components.StateStorage;
import com.intellij.openapi.components.StateStorageException;
import com.intellij.openapi.components.Storage;
import com.intellij.openapi.diagnostic.Logger;
import com.intellij.openapi.extensions.Extensions;
import com.intellij.openapi.module.Module;
import com.intellij.openapi.options.ConfigurationException;
import com.intellij.openapi.project.ProjectBundle;
import com.intellij.openapi.project.impl.ProjectMacrosUtil;
import com.intellij.openapi.roots.ModifiableRootModel;
import com.intellij.openapi.roots.ModuleRootManager;
import com.intellij.openapi.roots.ModuleRootModel;
import com.intellij.openapi.roots.impl.ModuleRootManagerImpl;
import com.intellij.openapi.util.InvalidDataException;
import com.intellij.openapi.util.Pair;
import com.intellij.openapi.util.Ref;
import com.intellij.openapi.util.WriteExternalException;
import com.intellij.openapi.util.io.FileUtil;
import com.intellij.openapi.vfs.*;
import com.intellij.openapi.vfs.tracker.VirtualFileTracker;
import com.intellij.util.containers.ContainerUtil;
import com.intellij.util.messages.MessageBus;
import org.jdom.Element;
import org.jetbrains.annotations.Nls;
import org.jetbrains.annotations.NonNls;
import org.jetbrains.annotations.NotNull;
import org.jetbrains.annotations.Nullable;
import org.jetbrains.jps.model.serialization.JpsProjectLoader;
import java.io.File;
import java.io.IOException;
import java.util.*;
/**
* Created by IntelliJ IDEA.
* User: Vladislav.Kaznacheev
* Date: Mar 9, 2007
* Time: 1:42:06 PM
*/
public class ClasspathStorage implements StateStorage {
private static final Logger LOG = Logger.getInstance("#com.intellij.openapi.roots.impl.storage.ClasspathStorage");
@NonNls public static final String SPECIAL_STORAGE = "special";
public static final String DEFAULT_STORAGE_DESCR = ProjectBundle.message("project.roots.classpath.format.default.descr");
@NonNls public static final String CLASSPATH_DIR_OPTION = JpsProjectLoader.CLASSPATH_DIR_ATTRIBUTE;
@NonNls private static final String COMPONENT_TAG = "component";
private Object mySession;
private final ClasspathStorageProvider.ClasspathConverter myConverter;
public ClasspathStorage(Module module) {
myConverter = getProvider(ClassPathStorageUtil.getStorageType(module)).createConverter(module);
final MessageBus messageBus = module.getMessageBus();
final VirtualFileTracker virtualFileTracker =
(VirtualFileTracker)module.getPicoContainer().getComponentInstanceOfType(VirtualFileTracker.class);
if (virtualFileTracker != null && messageBus != null) {
final ArrayList<VirtualFile> files = new ArrayList<VirtualFile>();
try {
myConverter.getFileSet().listFiles(files);
for (VirtualFile file : files) {
final Listener listener = messageBus.syncPublisher(STORAGE_TOPIC);
virtualFileTracker.addTracker(file.getUrl(), new VirtualFileAdapter() {
@Override
public void contentsChanged(@NotNull final VirtualFileEvent event) {
listener.storageFileChanged(event, ClasspathStorage.this);
}
}, true, module);
}
}
catch (UnsupportedOperationException e) {
//UnsupportedStorageProvider doesn't mean any files
}
}
}
private FileSet getFileSet() {
return myConverter.getFileSet();
}
@Override
@Nullable
public <T> T getState(final Object component, @NotNull final String componentName, Class<T> stateClass, @Nullable T mergeInto)
throws StateStorageException {
assert component instanceof ModuleRootManager;
assert componentName.equals("NewModuleRootManager");
assert stateClass == ModuleRootManagerImpl.ModuleRootManagerState.class;
try {
final Module module = ((ModuleRootManagerImpl)component).getModule();
final Element element = new Element(COMPONENT_TAG);
final Set<String> macros;
ModifiableRootModel model = null;
try {
model = ((ModuleRootManagerImpl)component).getModifiableModel();
macros = myConverter.getClasspath(model, element);
}
finally {
if (model != null) {
model.dispose();
}
}
final boolean macrosOk = ProjectMacrosUtil.checkNonIgnoredMacros(module.getProject(), macros);
PathMacroManager.getInstance(module).expandPaths(element);
ModuleRootManagerImpl.ModuleRootManagerState moduleRootManagerState = new ModuleRootManagerImpl.ModuleRootManagerState();
moduleRootManagerState.readExternal(element);
if (!macrosOk) {
throw new StateStorageException(ProjectBundle.message("project.load.undefined.path.variables.error"));
}
//noinspection unchecked
return (T)moduleRootManagerState;
}
catch (InvalidDataException e) {
throw new StateStorageException(e.getMessage());
}
catch (IOException e) {
throw new StateStorageException(e.getMessage());
}
}
@Override
public boolean hasState(final Object component, @NotNull final String componentName, final Class<?> aClass, final boolean reloadData)
throws StateStorageException {
return true;
}
public void setState(@NotNull Object component, @NotNull String componentName, @NotNull Object state) throws StateStorageException {
assert component instanceof ModuleRootManager;
assert componentName.equals("NewModuleRootManager");
assert state.getClass() == ModuleRootManagerImpl.ModuleRootManagerState.class;
try {
myConverter.setClasspath((ModuleRootManagerImpl)component);
}
catch (WriteExternalException e) {
throw new StateStorageException(e.getMessage());
}
catch (IOException e) {
throw new StateStorageException(e.getMessage());
}
}
@Override
@NotNull
public ExternalizationSession startExternalization() {
final ExternalizationSession session = new ExternalizationSession() {
@Override
public void setState(@NotNull final Object component, final String componentName, @NotNull final Object state, final Storage storageSpec)
throws StateStorageException {
assert mySession == this;
ClasspathStorage.this.setState(component, componentName, state);
}
};
mySession = session;
return session;
}
@Override
@NotNull
public SaveSession startSave(@NotNull final ExternalizationSession externalizationSession) {
assert mySession == externalizationSession;
final SaveSession session = new MySaveSession();
mySession = session;
return session;
}
private static void convert2Io(List<File> list, ArrayList<VirtualFile> virtualFiles) {
for (VirtualFile virtualFile : virtualFiles) {
list.add(VfsUtilCore.virtualToIoFile(virtualFile));
}
}
@Override
public void finishSave(@NotNull final SaveSession saveSession) {
try {
LOG.assertTrue(mySession == saveSession);
}
finally {
mySession = null;
}
}
@Override
public void reload(@NotNull final Set<String> changedComponents) throws StateStorageException {
}
public boolean needsSave() throws StateStorageException {
return getFileSet().hasChanged();
}
public void save() throws StateStorageException {
final Ref<IOException> ref = new Ref<IOException>();
ApplicationManager.getApplication().runWriteAction(new Runnable() {
@Override
public void run() {
try {
getFileSet().commit();
}
catch (IOException e) {
ref.set(e);
}
}
});
if (!ref.isNull()) {
throw new StateStorageException(ref.get());
}
}
@NotNull
public static ClasspathStorageProvider getProvider(@NotNull String type) {
for (ClasspathStorageProvider provider : getProviders()) {
if (type.equals(provider.getID())) {
return provider;
}
}
return new UnsupportedStorageProvider(type);
}
@NotNull
public static List<ClasspathStorageProvider> getProviders() {
final List<ClasspathStorageProvider> list = new ArrayList<ClasspathStorageProvider>();
list.add(new DefaultStorageProvider());
ContainerUtil.addAll(list, Extensions.getExtensions(ClasspathStorageProvider.EXTENSION_POINT_NAME));
return list;
}
@NotNull
public static String getModuleDir(@NotNull Module module) {
return new File(module.getModuleFilePath()).getParent();
}
public static String getStorageRootFromOptions(final Module module) {
final String moduleRoot = getModuleDir(module);
final String storageRef = module.getOptionValue(CLASSPATH_DIR_OPTION);
if (storageRef == null) {
return moduleRoot;
}
else if (FileUtil.isAbsolute(storageRef)) {
return storageRef;
}
else {
return FileUtil.toSystemIndependentName(new File(moduleRoot, storageRef).getPath());
}
}
public static void setStorageType(@NotNull ModuleRootModel model, @NotNull String storageID) {
final Module module = model.getModule();
final String oldStorageType = ClassPathStorageUtil.getStorageType(module);
if (oldStorageType.equals(storageID)) {
return;
}
getProvider(oldStorageType).detach(module);
if (storageID.equals(ClassPathStorageUtil.DEFAULT_STORAGE)) {
module.clearOption(ClassPathStorageUtil.CLASSPATH_OPTION);
module.clearOption(CLASSPATH_DIR_OPTION);
}
else {
module.setOption(ClassPathStorageUtil.CLASSPATH_OPTION, storageID);
module.setOption(CLASSPATH_DIR_OPTION, getProvider(storageID).getContentRoot(model));
}
}
public static void moduleRenamed(Module module, String newName) {
getProvider(ClassPathStorageUtil.getStorageType(module)).moduleRenamed(module, newName);
}
public static void modulePathChanged(Module module, String path) {
getProvider(ClassPathStorageUtil.getStorageType(module)).modulePathChanged(module, path);
}
private static class DefaultStorageProvider implements ClasspathStorageProvider {
@Override
@NonNls
public String getID() {
return ClassPathStorageUtil.DEFAULT_STORAGE;
}
@Override
@Nls
public String getDescription() {
return DEFAULT_STORAGE_DESCR;
}
@Override
public void assertCompatible(final ModuleRootModel model) throws ConfigurationException {
}
@Override
public void detach(Module module) {
}
@Override
public void moduleRenamed(Module module, String newName) {
//do nothing
}
@Override
public ClasspathConverter createConverter(Module module) {
throw new UnsupportedOperationException(getDescription());
}
@Override
public String getContentRoot(ModuleRootModel model) {
return null;
}
@Override
public void modulePathChanged(Module module, String path) {
}
}
public static class UnsupportedStorageProvider implements ClasspathStorageProvider {
private final String myType;
public UnsupportedStorageProvider(final String type) {
myType = type;
}
@Override
@NonNls
public String getID() {
return myType;
}
@Override
@Nls
public String getDescription() {
return "Unsupported classpath format " + myType;
}
@Override
public void assertCompatible(final ModuleRootModel model) throws ConfigurationException {
throw new UnsupportedOperationException(getDescription());
}
@Override
public void detach(final Module module) {
throw new UnsupportedOperationException(getDescription());
}
@Override
public void moduleRenamed(Module module, String newName) {
throw new UnsupportedOperationException(getDescription());
}
@Override
public ClasspathConverter createConverter(final Module module) {
return new ClasspathConverter() {
@Override
public FileSet getFileSet() {
throw new StateStorageException(getDescription());
}
@Override
public Set<String> getClasspath(final ModifiableRootModel model, final Element element) throws InvalidDataException {
throw new InvalidDataException(getDescription());
}
@Override
public void setClasspath(ModuleRootModel model) throws WriteExternalException {
throw new WriteExternalException(getDescription());
}
};
}
@Override
public String getContentRoot(ModuleRootModel model) {
return null;
}
@Override
public void modulePathChanged(Module module, String path) {
throw new UnsupportedOperationException(getDescription());
}
}
private class MySaveSession implements SaveSession, SafeWriteRequestor {
public boolean needsSave() throws StateStorageException {
assert mySession == this;
return ClasspathStorage.this.needsSave();
}
@Override
public void save() throws StateStorageException {
assert mySession == this;
ClasspathStorage.this.save();
}
@Override
@Nullable
public Set<String> analyzeExternalChanges(@NotNull final Set<Pair<VirtualFile, StateStorage>> changedFiles) {
return null;
}
@NotNull
@Override
public Collection<File> getStorageFilesToSave() throws StateStorageException {
if (needsSave()) {
final List<File> list = new ArrayList<File>();
final ArrayList<VirtualFile> virtualFiles = new ArrayList<VirtualFile>();
getFileSet().listModifiedFiles(virtualFiles);
convert2Io(list, virtualFiles);
return list;
}
else {
return Collections.emptyList();
}
}
@NotNull
@Override
public List<File> getAllStorageFiles() {
List<File> list = new ArrayList<File>();
ArrayList<VirtualFile> virtualFiles = new ArrayList<VirtualFile>();
getFileSet().listFiles(virtualFiles);
convert2Io(list, virtualFiles);
return list;
}
}
}