From d21b7062bb3012042d1feed039f70a6f929f4c1f Mon Sep 17 00:00:00 2001 From: Vladimir Krivosheev Date: Mon, 23 Feb 2015 16:24:53 +0100 Subject: [PATCH] Persistence state classes doesn't have to implement "equals" anymore MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit If you don’t implement “equals” in your sub state class, XmlSerializer cannot check it for defaults. TextDiffSettingsHolder.State.SHARED_SETTINGS = new SharedSettings(); The problem — “new SharedSettings()” is not equals to “new SharedSettings()”. As result, user gets even if SkipDefaultValuesSerializationFilters is used. While I dream about Kotlin, I can’t fight anymore against Java-developers. XmlSerializer can throw warning in this case, but to simplify developer life, it will compare objects by serializable fields, but not by reference (if equals is not implemented). --- .../compiler/CompilerConfigurationImpl.java | 6 +- .../src/com/intellij/ide/ui/UISettings.java | 2 +- .../PersistableCodeStyleSchemes.java | 4 +- .../impl/stores/SaveSessionBase.java | 6 +- .../com/intellij/util/xmlb/BeanBinding.java | 24 +++++- .../com/intellij/util/xmlb/FieldAccessor.java | 7 +- .../util/xmlb/SerializationFilter.java | 4 +- .../util/xmlb/SerializationFilterBase.java | 7 +- ...SkipDefaultValuesSerializationFilters.java | 3 +- .../xmlb/SkipDefaultsSerializationFilter.java | 85 +++++++++++++++++++ .../intellij/util/xmlb/SmartSerializer.java | 7 +- .../com/intellij/util/xmlb/XmlSerializer.java | 4 +- .../intellij/util/xmlb/XmlSerializerTest.java | 13 ++- .../XDebuggerBreakpointsContextProvider.java | 4 +- .../context/XDebuggerWatchesProvider.java | 4 +- 15 files changed, 142 insertions(+), 38 deletions(-) create mode 100644 platform/util/src/com/intellij/util/xmlb/SkipDefaultsSerializationFilter.java diff --git a/java/compiler/impl/src/com/intellij/compiler/CompilerConfigurationImpl.java b/java/compiler/impl/src/com/intellij/compiler/CompilerConfigurationImpl.java index 4edda91e2dd6..fc3cf13b92db 100644 --- a/java/compiler/impl/src/com/intellij/compiler/CompilerConfigurationImpl.java +++ b/java/compiler/impl/src/com/intellij/compiler/CompilerConfigurationImpl.java @@ -1,5 +1,5 @@ /* - * Copyright 2000-2014 JetBrains s.r.o. + * Copyright 2000-2015 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. @@ -142,11 +142,11 @@ public class CompilerConfigurationImpl extends CompilerConfiguration implements Element state = new Element("state"); XmlSerializer.serializeInto(myState, state, new SkipDefaultValuesSerializationFilters() { @Override - protected boolean accepts(@NotNull Accessor accessor, @NotNull Object bean, @Nullable Object beanValue) { + public boolean accepts(@NotNull Accessor accessor, @NotNull Object bean) { if (myState.compilerWasSpecified && "DEFAULT_COMPILER".equals(accessor.getName())) { return true; } - return super.accepts(accessor, bean, beanValue); + return super.accepts(accessor, bean); } }); diff --git a/platform/editor-ui-api/src/com/intellij/ide/ui/UISettings.java b/platform/editor-ui-api/src/com/intellij/ide/ui/UISettings.java index 055fbc8ca6d7..d2f9b41fde60 100644 --- a/platform/editor-ui-api/src/com/intellij/ide/ui/UISettings.java +++ b/platform/editor-ui-api/src/com/intellij/ide/ui/UISettings.java @@ -184,7 +184,7 @@ public class UISettings extends SimpleModificationTracker implements PersistentS public static class FontFilter implements SerializationFilter { @Override - public boolean accepts(@NotNull Accessor accessor, Object bean) { + public boolean accepts(@NotNull Accessor accessor, @NotNull Object bean) { UISettings settings = (UISettings)bean; return !hasDefaultFontSetting(settings); } diff --git a/platform/lang-impl/src/com/intellij/psi/impl/source/codeStyle/PersistableCodeStyleSchemes.java b/platform/lang-impl/src/com/intellij/psi/impl/source/codeStyle/PersistableCodeStyleSchemes.java index 11b3a65a1318..4cae3d3eda6e 100644 --- a/platform/lang-impl/src/com/intellij/psi/impl/source/codeStyle/PersistableCodeStyleSchemes.java +++ b/platform/lang-impl/src/com/intellij/psi/impl/source/codeStyle/PersistableCodeStyleSchemes.java @@ -49,9 +49,9 @@ class PersistableCodeStyleSchemes extends CodeStyleSchemesImpl implements Persis public Element getState() { return XmlSerializer.serialize(this, new SerializationFilter() { @Override - public boolean accepts(@NotNull Accessor accessor, Object bean) { + public boolean accepts(@NotNull Accessor accessor, @NotNull Object bean) { if ("CURRENT_SCHEME_NAME".equals(accessor.getName())) { - return bean != null && !DEFAULT_SCHEME_NAME.equals(accessor.read(bean)); + return !DEFAULT_SCHEME_NAME.equals(accessor.read(bean)); } else { return accessor.getValueClass().equals(String.class); diff --git a/platform/platform-impl/src/com/intellij/openapi/components/impl/stores/SaveSessionBase.java b/platform/platform-impl/src/com/intellij/openapi/components/impl/stores/SaveSessionBase.java index b46d88ea346b..9c100643314e 100644 --- a/platform/platform-impl/src/com/intellij/openapi/components/impl/stores/SaveSessionBase.java +++ b/platform/platform-impl/src/com/intellij/openapi/components/impl/stores/SaveSessionBase.java @@ -19,14 +19,14 @@ import com.intellij.openapi.components.StateStorage; import com.intellij.openapi.components.Storage; import com.intellij.openapi.util.JDOMExternalizable; import com.intellij.openapi.util.WriteExternalException; -import com.intellij.util.xmlb.SkipDefaultValuesSerializationFilters; +import com.intellij.util.xmlb.SkipDefaultsSerializationFilter; import com.intellij.util.xmlb.XmlSerializer; import org.jdom.Element; import org.jetbrains.annotations.NotNull; import org.jetbrains.annotations.Nullable; abstract class SaveSessionBase implements StateStorage.SaveSession, StateStorage.ExternalizationSession { - private SkipDefaultValuesSerializationFilters serializationFilter; + private SkipDefaultsSerializationFilter serializationFilter; @SuppressWarnings("deprecation") @Override @@ -42,7 +42,7 @@ abstract class SaveSessionBase implements StateStorage.SaveSession, StateStorage } else { if (serializationFilter == null) { - serializationFilter = new SkipDefaultValuesSerializationFilters(); + serializationFilter = new SkipDefaultsSerializationFilter(); } element = XmlSerializer.serializeIfNotDefault(state, serializationFilter); } diff --git a/platform/util/src/com/intellij/util/xmlb/BeanBinding.java b/platform/util/src/com/intellij/util/xmlb/BeanBinding.java index 884b7e2aa656..ad15638fff36 100644 --- a/platform/util/src/com/intellij/util/xmlb/BeanBinding.java +++ b/platform/util/src/com/intellij/util/xmlb/BeanBinding.java @@ -1,5 +1,5 @@ /* - * Copyright 2000-2014 JetBrains s.r.o. + * Copyright 2000-2015 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. @@ -39,7 +39,7 @@ import java.util.*; import java.util.List; class BeanBinding extends Binding { - private static final Logger LOG = Logger.getInstance(BeanBinding.class); + static final Logger LOG = Logger.getInstance(BeanBinding.class); private static final Map> ourAccessorCache = ContainerUtil.createConcurrentSoftValueMap(); @@ -47,7 +47,7 @@ class BeanBinding extends Binding { @SuppressWarnings("FieldAccessedSynchronizedAndUnsynchronized") private Binding[] myBindings; - private final Class myBeanClass; + final Class myBeanClass; public BeanBinding(@NotNull Class beanClass, @Nullable Accessor accessor) { super(accessor); @@ -86,7 +86,13 @@ class BeanBinding extends Binding { public Element serializeInto(@NotNull Object o, @Nullable Element element, @NotNull SerializationFilter filter) { for (Binding binding : myBindings) { Accessor accessor = binding.getAccessor(); - if (!filter.accepts(accessor, o)) { + + if (filter instanceof SkipDefaultsSerializationFilter) { + if (((SkipDefaultsSerializationFilter)filter).equal(binding, o)) { + continue; + } + } + else if (!filter.accepts(accessor, o)) { continue; } @@ -121,6 +127,16 @@ class BeanBinding extends Binding { return instance; } + boolean equalByFields(@NotNull Object currentValue, @NotNull Object defaultValue, @NotNull SkipDefaultsSerializationFilter filter) { + for (Binding binding : myBindings) { + Accessor accessor = binding.getAccessor(); + if (!filter.equal(binding, accessor.read(currentValue), accessor.read(defaultValue))) { + return false; + } + } + return true; + } + @NotNull public TObjectFloatHashMap computeBindingWeights(@NotNull LinkedHashSet accessorNameTracker) { TObjectFloatHashMap weights = new TObjectFloatHashMap(accessorNameTracker.size()); diff --git a/platform/util/src/com/intellij/util/xmlb/FieldAccessor.java b/platform/util/src/com/intellij/util/xmlb/FieldAccessor.java index c7fd18abad65..fdb1eab69768 100644 --- a/platform/util/src/com/intellij/util/xmlb/FieldAccessor.java +++ b/platform/util/src/com/intellij/util/xmlb/FieldAccessor.java @@ -1,5 +1,5 @@ /* - * Copyright 2000-2014 JetBrains s.r.o. + * Copyright 2000-2015 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. @@ -13,7 +13,6 @@ * See the License for the specific language governing permissions and * limitations under the License. */ - package com.intellij.util.xmlb; import org.jetbrains.annotations.NonNls; @@ -39,7 +38,7 @@ class FieldAccessor implements Accessor { return myField.get(o); } catch (IllegalAccessException e) { - throw new XmlSerializationException("Reading "+myField, e); + throw new XmlSerializationException("Reading " + myField, e); } } @@ -52,7 +51,7 @@ class FieldAccessor implements Accessor { myField.set(o, XmlSerializerImpl.convert(value, type)); } catch (IllegalAccessException e) { - throw new XmlSerializationException("Writing "+myField,e); + throw new XmlSerializationException("Writing " + myField, e); } } diff --git a/platform/util/src/com/intellij/util/xmlb/SerializationFilter.java b/platform/util/src/com/intellij/util/xmlb/SerializationFilter.java index 01f6f68f4fd1..f12f3647952f 100644 --- a/platform/util/src/com/intellij/util/xmlb/SerializationFilter.java +++ b/platform/util/src/com/intellij/util/xmlb/SerializationFilter.java @@ -1,5 +1,5 @@ /* - * Copyright 2000-2014 JetBrains s.r.o. + * Copyright 2000-2015 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. @@ -18,5 +18,5 @@ package com.intellij.util.xmlb; import org.jetbrains.annotations.NotNull; public interface SerializationFilter { - boolean accepts(@NotNull Accessor accessor, Object bean); + boolean accepts(@NotNull Accessor accessor, @NotNull Object bean); } diff --git a/platform/util/src/com/intellij/util/xmlb/SerializationFilterBase.java b/platform/util/src/com/intellij/util/xmlb/SerializationFilterBase.java index 7c1cb2b51795..6731bcaf7698 100644 --- a/platform/util/src/com/intellij/util/xmlb/SerializationFilterBase.java +++ b/platform/util/src/com/intellij/util/xmlb/SerializationFilterBase.java @@ -1,5 +1,5 @@ /* - * Copyright 2000-2014 JetBrains s.r.o. + * Copyright 2000-2015 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. @@ -20,10 +20,7 @@ import org.jetbrains.annotations.Nullable; public abstract class SerializationFilterBase implements SerializationFilter { @Override - public final boolean accepts(@NotNull Accessor accessor, Object bean) { - if (bean == null) { - return true; - } + public boolean accepts(@NotNull Accessor accessor, @NotNull Object bean) { return accepts(accessor, bean, accessor.read(bean)); } diff --git a/platform/util/src/com/intellij/util/xmlb/SkipDefaultValuesSerializationFilters.java b/platform/util/src/com/intellij/util/xmlb/SkipDefaultValuesSerializationFilters.java index db5819a9cc01..559ad12f8dab 100644 --- a/platform/util/src/com/intellij/util/xmlb/SkipDefaultValuesSerializationFilters.java +++ b/platform/util/src/com/intellij/util/xmlb/SkipDefaultValuesSerializationFilters.java @@ -39,7 +39,8 @@ public class SkipDefaultValuesSerializationFilters extends SerializationFilterBa } } - private Object getDefaultBean(@NotNull Object bean) { + @NotNull + Object getDefaultBean(@NotNull Object bean) { Class c = bean.getClass(); Object o = myDefaultBeans.get(c); if (o == null) { diff --git a/platform/util/src/com/intellij/util/xmlb/SkipDefaultsSerializationFilter.java b/platform/util/src/com/intellij/util/xmlb/SkipDefaultsSerializationFilter.java new file mode 100644 index 000000000000..f138b621e2ad --- /dev/null +++ b/platform/util/src/com/intellij/util/xmlb/SkipDefaultsSerializationFilter.java @@ -0,0 +1,85 @@ +/* + * Copyright 2000-2015 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.util.xmlb; + +import com.intellij.openapi.util.Comparing; +import com.intellij.openapi.util.JDOMUtil; +import com.intellij.util.ThreeState; +import gnu.trove.THashMap; +import org.jdom.Element; +import org.jetbrains.annotations.NotNull; +import org.jetbrains.annotations.Nullable; + +import java.util.Map; + +public final class SkipDefaultsSerializationFilter extends SkipDefaultValuesSerializationFilters { + private Map, ThreeState> hasEqualMethod; + + boolean equal(@NotNull Binding binding, @NotNull Object bean) { + Accessor accessor = binding.getAccessor(); + return equal(binding, accessor.read(bean), accessor.read(getDefaultBean(bean))); + } + + boolean equal(@Nullable Binding binding, @Nullable Object currentValue, @Nullable Object defaultValue) { + if (defaultValue instanceof Element && currentValue instanceof Element) { + return JDOMUtil.areElementsEqual((Element)currentValue, (Element)defaultValue); + } + else { + if (currentValue == defaultValue) { + return true; + } + if (currentValue == null || defaultValue == null) { + return false; + } + + if (binding instanceof BasePrimitiveBinding) { + Binding referencedBinding = ((BasePrimitiveBinding)binding).myBinding; + if (referencedBinding instanceof BeanBinding) { + Class referencedBeanClass = ((BeanBinding)referencedBinding).myBeanClass; + ThreeState compareByFields; + if (hasEqualMethod == null) { + compareByFields = null; + hasEqualMethod = new THashMap, ThreeState>(); + } + else { + compareByFields = hasEqualMethod.get(referencedBeanClass); + } + + if (compareByFields == null) { + try { + referencedBeanClass.getDeclaredMethod("equals", Object.class); + compareByFields = ThreeState.NO; + hasEqualMethod.put(referencedBeanClass, compareByFields); + } + catch (NoSuchMethodException ignored) { + compareByFields = ThreeState.YES; + hasEqualMethod.put(referencedBeanClass, compareByFields); + } + catch (Exception e) { + BeanBinding.LOG.warn(e); + } + } + + if (compareByFields == ThreeState.YES) { + return ((BeanBinding)referencedBinding).equalByFields(currentValue, defaultValue, this); + } + } + } + + return Comparing.equal(currentValue, defaultValue); + } + } +} diff --git a/platform/util/src/com/intellij/util/xmlb/SmartSerializer.java b/platform/util/src/com/intellij/util/xmlb/SmartSerializer.java index f254f451fcd1..c3af4340bf41 100644 --- a/platform/util/src/com/intellij/util/xmlb/SmartSerializer.java +++ b/platform/util/src/com/intellij/util/xmlb/SmartSerializer.java @@ -1,5 +1,5 @@ /* - * Copyright 2000-2014 JetBrains s.r.o. + * Copyright 2000-2015 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. @@ -19,7 +19,6 @@ import com.intellij.util.ThreeState; import gnu.trove.TObjectFloatHashMap; import org.jdom.Element; import org.jetbrains.annotations.NotNull; -import org.jetbrains.annotations.Nullable; import java.util.LinkedHashSet; @@ -40,11 +39,11 @@ public final class SmartSerializer { } : new SkipDefaultValuesSerializationFilters() { @Override - protected boolean accepts(@NotNull Accessor accessor, @NotNull Object bean, @Nullable Object beanValue) { + public boolean accepts(@NotNull Accessor accessor, @NotNull Object bean) { if (mySerializedAccessorNameTracker != null && mySerializedAccessorNameTracker.contains(accessor.getName())) { return true; } - return super.accepts(accessor, bean, beanValue); + return super.accepts(accessor, bean); } }; } diff --git a/platform/util/src/com/intellij/util/xmlb/XmlSerializer.java b/platform/util/src/com/intellij/util/xmlb/XmlSerializer.java index 97e4a35276a5..41a3e86a5a43 100644 --- a/platform/util/src/com/intellij/util/xmlb/XmlSerializer.java +++ b/platform/util/src/com/intellij/util/xmlb/XmlSerializer.java @@ -1,5 +1,5 @@ /* - * Copyright 2000-2009 JetBrains s.r.o. + * Copyright 2000-2015 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. @@ -31,7 +31,7 @@ import java.util.Set; public class XmlSerializer { private static final SerializationFilter TRUE_FILTER = new SerializationFilter() { @Override - public boolean accepts(@NotNull Accessor accessor, Object bean) { + public boolean accepts(@NotNull Accessor accessor, @NotNull Object bean) { return true; } }; diff --git a/platform/util/testSrc/com/intellij/util/xmlb/XmlSerializerTest.java b/platform/util/testSrc/com/intellij/util/xmlb/XmlSerializerTest.java index d518b9c5828a..c2108d320ea8 100644 --- a/platform/util/testSrc/com/intellij/util/xmlb/XmlSerializerTest.java +++ b/platform/util/testSrc/com/intellij/util/xmlb/XmlSerializerTest.java @@ -1,5 +1,5 @@ /* - * Copyright 2000-2014 JetBrains s.r.o. + * Copyright 2000-2015 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. @@ -158,6 +158,13 @@ public class XmlSerializerTest extends TestCase { bean); } + public void testSubBeanSerializationAndSkipDefaults() { + BeanWithSubBean bean = new BeanWithSubBean(); + doSerializerTest( + "", + bean, new SkipDefaultsSerializationFilter()); + } + public void testNullFieldValue() { BeanWithPublicFields bean1 = new BeanWithPublicFields(); @@ -538,7 +545,7 @@ public class XmlSerializerTest extends TestCase { "", new SerializationFilter() { @Override - public boolean accepts(@NotNull Accessor accessor, Object bean) { + public boolean accepts(@NotNull Accessor accessor, @NotNull Object bean) { return accessor.getName().startsWith("I"); } }); @@ -851,7 +858,7 @@ public class XmlSerializerTest extends TestCase { } public static class PropertyFilterTest implements SerializationFilter { @Override - public boolean accepts(@NotNull Accessor accessor, Object bean) { + public boolean accepts(@NotNull Accessor accessor, @NotNull Object bean) { return !accessor.read(bean).equals("skip"); } } diff --git a/plugins/tasks/tasks-core/src/com/intellij/tasks/context/XDebuggerBreakpointsContextProvider.java b/plugins/tasks/tasks-core/src/com/intellij/tasks/context/XDebuggerBreakpointsContextProvider.java index ec4d81f86d2c..5dabf0b91e3b 100644 --- a/plugins/tasks/tasks-core/src/com/intellij/tasks/context/XDebuggerBreakpointsContextProvider.java +++ b/plugins/tasks/tasks-core/src/com/intellij/tasks/context/XDebuggerBreakpointsContextProvider.java @@ -1,5 +1,5 @@ /* - * Copyright 2000-2013 JetBrains s.r.o. + * Copyright 2000-2015 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. @@ -56,7 +56,7 @@ public class XDebuggerBreakpointsContextProvider extends WorkingContextProvider XBreakpointManagerImpl.BreakpointManagerState state = myBreakpointManager.getState(); Element serialize = XmlSerializer.serialize(state, new SerializationFilter() { @Override - public boolean accepts(@NotNull Accessor accessor, Object bean) { + public boolean accepts(@NotNull Accessor accessor, @NotNull Object bean) { return accessor.read(bean) != null; } }); diff --git a/plugins/tasks/tasks-core/src/com/intellij/tasks/context/XDebuggerWatchesProvider.java b/plugins/tasks/tasks-core/src/com/intellij/tasks/context/XDebuggerWatchesProvider.java index 2b39f86d30b8..12fddd63d7f8 100644 --- a/plugins/tasks/tasks-core/src/com/intellij/tasks/context/XDebuggerWatchesProvider.java +++ b/plugins/tasks/tasks-core/src/com/intellij/tasks/context/XDebuggerWatchesProvider.java @@ -1,5 +1,5 @@ /* - * Copyright 2000-2014 JetBrains s.r.o. + * Copyright 2000-2015 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. @@ -54,7 +54,7 @@ public class XDebuggerWatchesProvider extends WorkingContextProvider { XDebuggerWatchesManager.WatchesManagerState state = myWatchesManager.getState(); Element serialize = XmlSerializer.serialize(state, new SerializationFilter() { @Override - public boolean accepts(@NotNull Accessor accessor, Object bean) { + public boolean accepts(@NotNull Accessor accessor, @NotNull Object bean) { return accessor.read(bean) != null; } });