IDEA-166461 Cannot revert module language level to 1.8

Introduce StoredProperty and BaseState as approach to simplfy creating trackable (modified, differs from default) state classes.
This commit is contained in:
Vladimir Krivosheev
2017-01-13 13:44:19 +01:00
parent 205a7adb92
commit ca6c5d0aea
10 changed files with 229 additions and 40 deletions

View File

@@ -10,6 +10,6 @@
<orderEntry type="module" module-name="analysis-api" />
<orderEntry type="module" module-name="java-psi-api" />
<orderEntry type="module" module-name="projectModel-api" />
<orderEntry type="library" name="KotlinJavaRuntime" level="project" />
</component>
</module>
</module>

View File

@@ -20,24 +20,29 @@
*/
package com.intellij.openapi.roots;
import com.intellij.openapi.components.PersistentStateComponentWithModificationTracker;
import com.intellij.openapi.diagnostic.Logger;
import com.intellij.openapi.module.Module;
import com.intellij.pom.java.LanguageLevel;
import org.jdom.Element;
import org.jetbrains.annotations.NonNls;
import org.jetbrains.annotations.NotNull;
import org.jetbrains.annotations.Nullable;
public class LanguageLevelModuleExtensionImpl extends ModuleExtension implements LanguageLevelModuleExtension {
public class LanguageLevelModuleExtensionImpl extends ModuleExtension implements LanguageLevelModuleExtension,
PersistentStateComponentWithModificationTracker<LanguageLevelState> {
private static final Logger LOG = Logger.getInstance(LanguageLevelModuleExtensionImpl.class);
@NonNls private static final String LANGUAGE_LEVEL_ELEMENT_NAME = "LANGUAGE_LEVEL";
private Module myModule;
private final boolean myWritable;
private LanguageLevel myLanguageLevel;
private final LanguageLevelModuleExtensionImpl mySource;
private LanguageLevelState myState = new LanguageLevelState();
@Override
public long getStateModificationCount() {
return myState.getModificationCount();
}
public static LanguageLevelModuleExtensionImpl getInstance(final Module module) {
return ModuleRootManager.getInstance(module).getModuleExtension(LanguageLevelModuleExtensionImpl.class);
}
@@ -51,37 +56,31 @@ public class LanguageLevelModuleExtensionImpl extends ModuleExtension implements
public LanguageLevelModuleExtensionImpl(final LanguageLevelModuleExtensionImpl source, boolean writable) {
myWritable = writable;
myModule = source.myModule;
myLanguageLevel = source.myLanguageLevel;
mySource = source;
// setter must be used instead of creating new state with constructor param because in any case default language level for module is null (i.e. project language level)
myState.setLanguageLevel(source.getLanguageLevel());
}
@Override
public void setLanguageLevel(final LanguageLevel languageLevel) {
LOG.assertTrue(myWritable, "Writable model can be retrieved from writable ModifiableRootModel");
myLanguageLevel = languageLevel;
myState.setLanguageLevel(languageLevel);
}
@Nullable
@Override
public LanguageLevelState getState() {
return myState;
}
@Override
public void readExternal(@NotNull Element element) {
final String languageLevel = element.getAttributeValue(LANGUAGE_LEVEL_ELEMENT_NAME);
if (languageLevel != null) {
try {
myLanguageLevel = LanguageLevel.valueOf(languageLevel);
}
catch (IllegalArgumentException e) {
//bad value was stored
}
}
else {
myLanguageLevel = null;
}
public void loadState(LanguageLevelState state) {
myState = state;
}
@Override
public void writeExternal(final Element element) {
if (myLanguageLevel != null) {
element.setAttribute(LANGUAGE_LEVEL_ELEMENT_NAME, myLanguageLevel.toString());
}
public int compareTo(@NotNull Object o) {
return 0;
}
@Override
@@ -91,26 +90,26 @@ public class LanguageLevelModuleExtensionImpl extends ModuleExtension implements
@Override
public void commit() {
if (mySource != null && mySource.myLanguageLevel != myLanguageLevel) {
mySource.myLanguageLevel = myLanguageLevel;
if (isChanged()) {
mySource.myState.setLanguageLevel(myState.getLanguageLevel());
LanguageLevelProjectExtension.getInstance(myModule.getProject()).languageLevelsChanged();
}
}
@Override
public boolean isChanged() {
return mySource != null && mySource.myLanguageLevel != myLanguageLevel;
return mySource != null && !mySource.myState.equals(myState);
}
@Override
public void dispose() {
myModule = null;
myLanguageLevel = null;
myState = null;
}
@Nullable
@Override
public LanguageLevel getLanguageLevel() {
return myLanguageLevel;
return myState.getLanguageLevel();
}
}

View File

@@ -0,0 +1,25 @@
/*
* Copyright 2000-2017 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
import com.intellij.openapi.components.BaseState
import com.intellij.pom.java.LanguageLevel
import com.intellij.util.xmlb.annotations.Attribute
class LanguageLevelState : BaseState() {
@get:Attribute("LANGUAGE_LEVEL")
var languageLevel: LanguageLevel? by storedProperty()
}

View File

@@ -0,0 +1,36 @@
package com.intellij.configurationStore
import com.intellij.openapi.components.BaseState
import com.intellij.testFramework.assertions.Assertions.assertThat
import com.intellij.util.loadElement
import com.intellij.util.xmlb.XmlSerializer
import com.intellij.util.xmlb.annotations.Attribute
import org.junit.Test
private class AState : BaseState() {
@get:Attribute("customName")
var languageLevel: String? by storedProperty()
var property2 by storedProperty(0)
}
class StoredPropertyStateTest {
@Test
fun test() {
val state = AState()
assertThat(state).isEqualTo(AState())
assertThat(state.modificationCount).isEqualTo(0)
assertThat(state.languageLevel).isNull()
state.languageLevel = "foo"
assertThat(state.modificationCount).isEqualTo(1)
assertThat(state.languageLevel).isEqualTo("foo")
assertThat(state.modificationCount).isEqualTo(1)
assertThat(state).isNotEqualTo(AState())
assertThat(XmlSerializer.serialize(state)).isEqualTo("""<AState customName="foo" />""")
assertThat(XmlSerializer.deserialize(loadElement("""<AState customName="foo" />"""), AState::class.java)!!.languageLevel).isEqualTo("foo")
}
}

View File

@@ -1,5 +1,5 @@
/*
* Copyright 2000-2016 JetBrains s.r.o.
* Copyright 2000-2017 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.
@@ -78,6 +78,6 @@ public class ModuleRootManagerComponent extends ModuleRootManagerImpl implements
}
return true;
});
return result[0];
return result[0] + myRootModel.getStateModificationCount();
}
}

View File

@@ -0,0 +1,79 @@
/*
* Copyright 2000-2017 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.components
import com.intellij.openapi.util.SimpleModificationTracker
import com.intellij.util.SmartList
import com.intellij.util.xmlb.Accessor
import com.intellij.util.xmlb.SerializationFilter
import kotlin.properties.ReadWriteProperty
import kotlin.reflect.KProperty
abstract class BaseState : SimpleModificationTracker(), SerializationFilter {
// if property value differs from default
private val properties: MutableList<StoredProperty<*>> = SmartList()
override fun accepts(accessor: Accessor, bean: Any): Boolean {
for (property in properties) {
if (property.name == accessor.name) {
return property.value != property.defaultValue
}
}
return false
}
fun <T : Any> storedProperty(defaultValue: T? = null): ReadWriteProperty<BaseState, T?> {
val result = StoredProperty(defaultValue)
properties.add(result)
return result
}
override fun equals(other: Any?): Boolean {
if (this === other) return true
if (other !is BaseState) return false
return properties == other.properties
}
override fun hashCode() = properties.hashCode()
}
internal class StoredProperty<T>(internal val defaultValue: T?) : ReadWriteProperty<BaseState, T?> {
internal var value = defaultValue
internal var name: String? = null
override operator fun getValue(thisRef: BaseState, property: KProperty<*>) = value
@Suppress("UNCHECKED_CAST")
override fun setValue(thisRef: BaseState, property: KProperty<*>, @Suppress("PARAMETER_NAME_CHANGED_ON_OVERRIDE") newValue: T?) {
if (value != newValue) {
thisRef.incModificationCount()
name = property.name
value = newValue
}
}
override fun equals(other: Any?): Boolean {
if (this === other) return true
if (other !is StoredProperty<*>) return false
if (value != other.value) return false
return true
}
override fun hashCode() = value?.hashCode() ?: 0
}

View File

@@ -1,5 +1,5 @@
/*
* Copyright 2000-2009 JetBrains s.r.o.
* Copyright 2000-2017 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.
@@ -22,10 +22,13 @@ package com.intellij.openapi.roots;
import com.intellij.openapi.Disposable;
import com.intellij.openapi.extensions.ExtensionPointName;
import com.intellij.openapi.util.JDOMExternalizable;
import org.jdom.Element;
import org.jetbrains.annotations.NotNull;
public abstract class ModuleExtension<T extends ModuleExtension> implements JDOMExternalizable, Disposable, Comparable<ModuleExtension> {
/**
* Implement {@link com.intellij.openapi.components.PersistentStateComponent} to be serializable.
*/
public abstract class ModuleExtension<T extends ModuleExtension> implements Disposable, Comparable<ModuleExtension> {
public static final ExtensionPointName<ModuleExtension> EP_NAME = ExtensionPointName.create("com.intellij.moduleExtension");
/**
@@ -58,4 +61,20 @@ public abstract class ModuleExtension<T extends ModuleExtension> implements JDOM
public int compareTo(@NotNull final ModuleExtension o) {
return getClass().getName().compareTo(o.getClass().getName());
}
/**
* @deprecated Please implement PersistentStateComponent instead.
*/
@Deprecated
public void readExternal(@NotNull Element element) {
throw new Error("Please implement PersistentStateComponent");
}
/**
* @deprecated Please implement PersistentStateComponent instead.
*/
@Deprecated
public void writeExternal(@NotNull Element element) {
throw new Error("Please implement PersistentStateComponent");
}
}

View File

@@ -51,7 +51,7 @@ public class ModuleRootManagerImpl extends ModuleRootManager implements Disposab
private final Module myModule;
private final ProjectRootManagerImpl myProjectRootManager;
private final VirtualFilePointerManager myFilePointerManager;
private RootModelImpl myRootModel;
protected RootModelImpl myRootModel;
private boolean myIsDisposed = false;
private boolean myLoaded = false;
private final OrderRootsCache myOrderRootsCache;

View File

@@ -1,5 +1,5 @@
/*
* Copyright 2000-2015 JetBrains s.r.o.
* Copyright 2000-2017 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.
@@ -17,6 +17,9 @@ package com.intellij.openapi.roots.impl;
import com.intellij.openapi.CompositeDisposable;
import com.intellij.openapi.Disposable;
import com.intellij.openapi.components.ComponentSerializationUtil;
import com.intellij.openapi.components.PersistentStateComponent;
import com.intellij.openapi.components.PersistentStateComponentWithModificationTracker;
import com.intellij.openapi.diagnostic.Logger;
import com.intellij.openapi.extensions.Extensions;
import com.intellij.openapi.module.Module;
@@ -34,6 +37,7 @@ import com.intellij.openapi.vfs.pointers.VirtualFilePointerManager;
import com.intellij.util.ArrayUtil;
import com.intellij.util.ObjectUtils;
import com.intellij.util.containers.ContainerUtil;
import com.intellij.util.xmlb.XmlSerializer;
import org.jdom.Element;
import org.jetbrains.annotations.NotNull;
import org.jetbrains.annotations.Nullable;
@@ -122,7 +126,15 @@ public class RootModelImpl extends RootModelBase implements ModifiableRootModel
RootModelImpl originalRootModel = moduleRootManager.getRootModel();
for (ModuleExtension extension : originalRootModel.myExtensions) {
ModuleExtension model = extension.getModifiableModel(false);
model.readExternal(element);
if (model instanceof PersistentStateComponent) {
PersistentStateComponent<?> component = (PersistentStateComponent)model;
component.loadState(XmlSerializer.deserialize(element, ComponentSerializationUtil.getStateClass((component.getClass()))));
}
else {
model.readExternal(element);
}
registerOnDispose(model);
myExtensions.add(model);
}
@@ -407,9 +419,24 @@ public class RootModelImpl extends RootModelBase implements ModifiableRootModel
return e;
}
public long getStateModificationCount() {
long result = 0;
for (ModuleExtension extension : myExtensions) {
if (extension instanceof PersistentStateComponentWithModificationTracker) {
result += ((PersistentStateComponentWithModificationTracker)extension).getStateModificationCount();
}
}
return result;
}
public void writeExternal(@NotNull Element element) {
for (ModuleExtension extension : myExtensions) {
extension.writeExternal(element);
if (extension instanceof PersistentStateComponent) {
XmlSerializer.serializeInto(((PersistentStateComponent)extension).getState(), element);
}
else {
extension.writeExternal(element);
}
}
for (ContentEntry contentEntry : getContent()) {

View File

@@ -89,6 +89,10 @@ class BeanBinding extends Binding {
for (Binding binding : myBindings) {
Accessor accessor = binding.getAccessor();
if (o instanceof SerializationFilter && !((SerializationFilter)o).accepts(accessor, o)) {
continue;
}
if (filter instanceof SkipDefaultsSerializationFilter) {
if (((SkipDefaultsSerializationFilter)filter).equal(binding, o)) {
continue;