blob: 2f8f22913b9a651d3f39e852451c90bf74798ba1 [file] [log] [blame]
/*
* Copyright 2000-2012 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.projectRoots.impl;
import com.intellij.openapi.Disposable;
import com.intellij.openapi.application.ApplicationManager;
import com.intellij.openapi.diagnostic.Logger;
import com.intellij.openapi.projectRoots.*;
import com.intellij.openapi.projectRoots.ex.ProjectRoot;
import com.intellij.openapi.projectRoots.ex.ProjectRootContainer;
import com.intellij.openapi.roots.OrderRootType;
import com.intellij.openapi.roots.RootProvider;
import com.intellij.openapi.roots.impl.RootProviderBaseImpl;
import com.intellij.openapi.util.*;
import com.intellij.openapi.vfs.StandardFileSystems;
import com.intellij.openapi.vfs.VfsUtilCore;
import com.intellij.openapi.vfs.VirtualFile;
import com.intellij.openapi.vfs.VirtualFileManager;
import com.intellij.util.ArrayUtil;
import com.intellij.util.containers.ContainerUtil;
import org.jdom.Element;
import org.jetbrains.annotations.NonNls;
import org.jetbrains.annotations.NotNull;
import org.jetbrains.annotations.Nullable;
import java.util.ArrayList;
import java.util.List;
public class ProjectJdkImpl extends UserDataHolderBase implements JDOMExternalizable, Sdk, SdkModificator {
private static final Logger LOG = Logger.getInstance("#com.intellij.openapi.projectRoots.impl.ProjectJdkImpl");
private final ProjectRootContainerImpl myRootContainer;
private String myName;
private String myVersionString;
private boolean myVersionDefined = false;
private String myHomePath = "";
private final MyRootProvider myRootProvider = new MyRootProvider();
private ProjectJdkImpl myOrigin = null;
private SdkAdditionalData myAdditionalData = null;
private SdkTypeId mySdkType;
@NonNls public static final String ELEMENT_NAME = "name";
@NonNls public static final String ATTRIBUTE_VALUE = "value";
@NonNls public static final String ELEMENT_TYPE = "type";
@NonNls public static final String ELEMENT_VERSION = "version";
@NonNls private static final String ELEMENT_ROOTS = "roots";
@NonNls private static final String ELEMENT_ROOT = "root";
@NonNls private static final String ELEMENT_PROPERTY = "property";
@NonNls private static final String VALUE_JDKHOME = "jdkHome";
@NonNls private static final String ATTRIBUTE_FILE = "file";
@NonNls public static final String ELEMENT_HOMEPATH = "homePath";
@NonNls private static final String ELEMENT_ADDITIONAL = "additional";
public ProjectJdkImpl(String name, SdkTypeId sdkType) {
mySdkType = sdkType;
myRootContainer = new ProjectRootContainerImpl(true);
myName = name;
myRootContainer.addProjectRootContainerListener(myRootProvider);
}
public ProjectJdkImpl(String name, SdkTypeId sdkType, String homePath, String version) {
this(name, sdkType);
myHomePath = homePath;
myVersionString = version;
}
@Override
@NotNull
public SdkTypeId getSdkType() {
if (mySdkType == null) {
mySdkType = ProjectJdkTable.getInstance().getDefaultSdkType();
}
return mySdkType;
}
@Override
@NotNull
public String getName() {
return myName;
}
@Override
public void setName(@NotNull String name) {
myName = name;
}
@Override
public final void setVersionString(@Nullable String versionString) {
myVersionString = versionString == null || versionString.isEmpty() ? null : versionString;
myVersionDefined = true;
}
@Override
public String getVersionString() {
if (myVersionString == null && !myVersionDefined) {
String homePath = getHomePath();
if (homePath != null && !homePath.isEmpty()) {
setVersionString(getSdkType().getVersionString(this));
}
}
return myVersionString;
}
public final void resetVersionString() {
myVersionDefined = false;
myVersionString = null;
}
@Override
public String getHomePath() {
return myHomePath;
}
@Override
public VirtualFile getHomeDirectory() {
if (myHomePath == null) {
return null;
}
return StandardFileSystems.local().findFileByPath(myHomePath);
}
@Override
public void readExternal(Element element) throws InvalidDataException {
myName = element.getChild(ELEMENT_NAME).getAttributeValue(ATTRIBUTE_VALUE);
final Element typeChild = element.getChild(ELEMENT_TYPE);
final String sdkTypeName = typeChild != null? typeChild.getAttributeValue(ATTRIBUTE_VALUE) : null;
if (sdkTypeName != null) {
mySdkType = ProjectJdkTable.getInstance().getSdkTypeByName(sdkTypeName);
}
final Element version = element.getChild(ELEMENT_VERSION);
// set version if it was cached (defined)
// otherwise it will be null && undefined
if (version != null) {
setVersionString(version.getAttributeValue(ATTRIBUTE_VALUE));
}
else {
myVersionDefined = false;
}
if (element.getAttribute(ELEMENT_VERSION) == null || !"2".equals(element.getAttributeValue(ELEMENT_VERSION))) {
myRootContainer.startChange();
myRootContainer.readOldVersion(element.getChild(ELEMENT_ROOTS));
final List children = element.getChild(ELEMENT_ROOTS).getChildren(ELEMENT_ROOT);
for (final Object aChildren : children) {
Element root = (Element)aChildren;
for (final Object o : root.getChildren(ELEMENT_PROPERTY)) {
Element prop = (Element)o;
if (ELEMENT_TYPE.equals(prop.getAttributeValue(ELEMENT_NAME)) && VALUE_JDKHOME.equals(prop.getAttributeValue(ATTRIBUTE_VALUE))) {
myHomePath = VirtualFileManager.extractPath(root.getAttributeValue(ATTRIBUTE_FILE));
}
}
}
myRootContainer.finishChange();
}
else {
myHomePath = element.getChild(ELEMENT_HOMEPATH).getAttributeValue(ATTRIBUTE_VALUE);
myRootContainer.readExternal(element.getChild(ELEMENT_ROOTS));
}
final Element additional = element.getChild(ELEMENT_ADDITIONAL);
if (additional != null) {
LOG.assertTrue(mySdkType != null);
myAdditionalData = mySdkType.loadAdditionalData(this, additional);
}
else {
myAdditionalData = null;
}
}
@Override
public void writeExternal(Element element) throws WriteExternalException {
element.setAttribute(ELEMENT_VERSION, "2");
final Element name = new Element(ELEMENT_NAME);
name.setAttribute(ATTRIBUTE_VALUE, myName);
element.addContent(name);
if (mySdkType != null) {
final Element sdkType = new Element(ELEMENT_TYPE);
sdkType.setAttribute(ATTRIBUTE_VALUE, mySdkType.getName());
element.addContent(sdkType);
}
if (myVersionString != null) {
final Element version = new Element(ELEMENT_VERSION);
version.setAttribute(ATTRIBUTE_VALUE, myVersionString);
element.addContent(version);
}
final Element home = new Element(ELEMENT_HOMEPATH);
home.setAttribute(ATTRIBUTE_VALUE, myHomePath);
element.addContent(home);
Element roots = new Element(ELEMENT_ROOTS);
myRootContainer.writeExternal(roots);
element.addContent(roots);
Element additional = new Element(ELEMENT_ADDITIONAL);
if (myAdditionalData != null) {
LOG.assertTrue(mySdkType != null);
mySdkType.saveAdditionalData(myAdditionalData, additional);
}
element.addContent(additional);
}
@Override
public void setHomePath(String path) {
final boolean changes = myHomePath == null? path != null : !myHomePath.equals(path);
myHomePath = path;
if (changes) {
myVersionString = null; // clear cached value if home path changed
myVersionDefined = false;
}
}
@Override
@NotNull
public Object clone() {
ProjectJdkImpl newJdk = new ProjectJdkImpl("", mySdkType);
copyTo(newJdk);
return newJdk;
}
@Override
@NotNull
public RootProvider getRootProvider() {
return myRootProvider;
}
public void copyTo(ProjectJdkImpl dest) {
final String name = getName();
dest.setName(name);
dest.setHomePath(getHomePath());
if (myVersionDefined) {
dest.setVersionString(getVersionString());
}
else {
dest.resetVersionString();
}
dest.setSdkAdditionalData(getSdkAdditionalData());
dest.myRootContainer.startChange();
dest.myRootContainer.removeAllRoots();
for(OrderRootType rootType: OrderRootType.getAllTypes()) {
copyRoots(myRootContainer, dest.myRootContainer, rootType);
}
dest.myRootContainer.finishChange();
}
private static void copyRoots(ProjectRootContainer srcContainer, ProjectRootContainer destContainer, OrderRootType type){
final ProjectRoot[] newRoots = srcContainer.getRoots(type);
for (ProjectRoot newRoot : newRoots) {
destContainer.addRoot(newRoot, type);
}
}
private class MyRootProvider extends RootProviderBaseImpl implements ProjectRootListener {
@Override
@NotNull
public String[] getUrls(@NotNull OrderRootType rootType) {
final ProjectRoot[] rootFiles = myRootContainer.getRoots(rootType);
final ArrayList<String> result = new ArrayList<String>();
for (ProjectRoot rootFile : rootFiles) {
ContainerUtil.addAll(result, rootFile.getUrls());
}
return ArrayUtil.toStringArray(result);
}
@Override
@NotNull
public VirtualFile[] getFiles(@NotNull final OrderRootType rootType) {
return myRootContainer.getRootFiles(rootType);
}
private final List<RootSetChangedListener> myListeners = ContainerUtil.createLockFreeCopyOnWriteList();
@Override
public void addRootSetChangedListener(@NotNull RootSetChangedListener listener) {
assert !myListeners.contains(listener);
myListeners.add(listener);
super.addRootSetChangedListener(listener);
}
@Override
public void addRootSetChangedListener(@NotNull final RootSetChangedListener listener, @NotNull Disposable parentDisposable) {
super.addRootSetChangedListener(listener, parentDisposable);
Disposer.register(parentDisposable, new Disposable() {
@Override
public void dispose() {
removeRootSetChangedListener(listener);
}
});
}
@Override
public void removeRootSetChangedListener(@NotNull RootSetChangedListener listener) {
super.removeRootSetChangedListener(listener);
myListeners.remove(listener);
}
@Override
public void rootsChanged() {
if (myListeners.isEmpty()) {
return;
}
ApplicationManager.getApplication().runWriteAction(new Runnable() {
@Override
public void run() {
fireRootSetChanged();
}
});
}
}
// SdkModificator implementation
@Override
@NotNull
public SdkModificator getSdkModificator() {
ProjectJdkImpl sdk = (ProjectJdkImpl)clone();
sdk.myOrigin = this;
sdk.myRootContainer.startChange();
sdk.update();
return sdk;
}
@Override
public void commitChanges() {
LOG.assertTrue(isWritable());
myRootContainer.finishChange();
copyTo(myOrigin);
myOrigin = null;
}
@Override
public SdkAdditionalData getSdkAdditionalData() {
return myAdditionalData;
}
@Override
public void setSdkAdditionalData(SdkAdditionalData data) {
myAdditionalData = data;
}
@Override
public VirtualFile[] getRoots(OrderRootType rootType) {
final ProjectRoot[] roots = myRootContainer.getRoots(rootType); // use getRoots() cause the data is most up-to-date there
final List<VirtualFile> files = new ArrayList<VirtualFile>(roots.length);
for (ProjectRoot root : roots) {
ContainerUtil.addAll(files, root.getVirtualFiles());
}
return VfsUtilCore.toVirtualFileArray(files);
}
@Override
public void addRoot(VirtualFile root, OrderRootType rootType) {
myRootContainer.addRoot(root, rootType);
}
@Override
public void removeRoot(VirtualFile root, OrderRootType rootType) {
myRootContainer.removeRoot(root, rootType);
}
@Override
public void removeRoots(OrderRootType rootType) {
myRootContainer.removeAllRoots(rootType);
}
@Override
public void removeAllRoots() {
myRootContainer.removeAllRoots();
}
@Override
public boolean isWritable() {
return myOrigin != null;
}
public void update() {
myRootContainer.update();
}
@Override
public String toString() {
return getName() + ": "+ getVersionString() + " ("+getHomePath()+")";
}
}