AppletConfiguration — don't save defaults — get rid of "module"

This commit is contained in:
Vladimir Krivosheev
2014-11-06 17:11:08 +01:00
parent 521d9ce235
commit 0443b40293
15 changed files with 207 additions and 59 deletions
@@ -35,6 +35,7 @@ import com.intellij.openapi.util.text.StringUtil;
import com.intellij.psi.PsiClass;
import com.intellij.psi.PsiElement;
import com.intellij.refactoring.listeners.RefactoringElementListener;
import com.intellij.util.SmartList;
import com.intellij.util.xmlb.SmartSerializer;
import com.intellij.util.xmlb.annotations.Transient;
import org.jdom.Element;
@@ -46,7 +47,6 @@ import java.io.FileWriter;
import java.io.IOException;
import java.net.MalformedURLException;
import java.net.URL;
import java.util.ArrayList;
import java.util.Collection;
import java.util.List;
@@ -74,7 +74,7 @@ public class AppletConfiguration extends ModuleBasedConfiguration<JavaRunConfigu
public AppletConfiguration(@NotNull Project project, ConfigurationFactory factory) {
super(new JavaRunConfigurationModule(project, false), factory);
mySerializer = new SmartSerializer(project.isDefault(), true);
mySerializer = new SmartSerializer(!project.isDefault(), true);
}
@Override
@@ -193,12 +193,18 @@ public class AppletConfiguration extends ModuleBasedConfiguration<JavaRunConfigu
@Override
public void readExternal(final Element parentNode) throws InvalidDataException {
mySerializer.readExternal(this, parentNode);
readModule(parentNode);
final ArrayList<AppletParameter> parameters = new ArrayList<AppletParameter>();
for (final Element element : parentNode.getChildren(PARAMETER_ELEMENT_NAME)) {
parameters.add(new AppletParameter(element.getAttributeValue(NAME_ATTR), element.getAttributeValue(VALUE_ATTR)));
List<Element> paramList = parentNode.getChildren(PARAMETER_ELEMENT_NAME);
if (paramList.isEmpty()) {
myAppletParameters = null;
}
else {
List<AppletParameter> parameters = new SmartList<AppletParameter>();
for (Element element : paramList) {
parameters.add(new AppletParameter(element.getAttributeValue(NAME_ATTR), element.getAttributeValue(VALUE_ATTR)));
}
myAppletParameters = parameters.toArray(new AppletParameter[parameters.size()]);
}
myAppletParameters = parameters.toArray(new AppletParameter[parameters.size()]);
}
@Override
@@ -208,7 +214,6 @@ public class AppletConfiguration extends ModuleBasedConfiguration<JavaRunConfigu
@Override
public void writeExternal(final Element parentNode) throws WriteExternalException {
writeModule(parentNode);
mySerializer.writeExternal(this, parentNode);
if (myAppletParameters != null) {
for (AppletParameter myAppletParameter : myAppletParameters) {
@@ -227,6 +232,7 @@ public class AppletConfiguration extends ModuleBasedConfiguration<JavaRunConfigu
}
@Override
@Transient
public PsiClass getMainClass() {
return getConfigurationModule().findClass(MAIN_CLASS_NAME);
}
@@ -23,14 +23,14 @@ import java.util.concurrent.atomic.AtomicIntegerFieldUpdater;
/**
* Created by Max Medvedev on 28/03/14
*/
@Transient
public class SimpleModificationTracker implements ModificationTracker {
static {
// field made public to workaround bug in JDK7 when AtomicIntegerFieldUpdater can't be created for private field, even from within its own class
// fixed in JDK8
assert Patches.JDK_BUG_ID_7103570;
}
@Transient
public volatile int myCounter;
@Override
@@ -24,6 +24,7 @@ import com.intellij.openapi.module.ModuleUtilCore;
import com.intellij.openapi.util.Computable;
import com.intellij.openapi.util.InvalidDataException;
import com.intellij.openapi.util.WriteExternalException;
import com.intellij.util.xmlb.annotations.Property;
import org.jdom.Element;
import org.jetbrains.annotations.NonNls;
import org.jetbrains.annotations.NotNull;
@@ -39,7 +40,10 @@ import java.util.Set;
*/
public abstract class ModuleBasedConfiguration<ConfigurationModule extends RunConfigurationModule> extends LocatableConfigurationBase implements Cloneable, ModuleRunConfiguration {
private static final Logger LOG = Logger.getInstance("#com.intellij.execution.configurations.ModuleBasedConfiguration");
@Property(surroundWithTag = false)
private final ConfigurationModule myModule;
@NonNls
protected static final String TO_CLONE_ELEMENT_NAME = "toClone";
@@ -26,6 +26,9 @@ import com.intellij.openapi.roots.ModuleRootManager;
import com.intellij.openapi.util.Computable;
import com.intellij.openapi.util.JDOMExternalizable;
import com.intellij.openapi.util.text.StringUtil;
import com.intellij.util.xmlb.annotations.Attribute;
import com.intellij.util.xmlb.annotations.Tag;
import com.intellij.util.xmlb.annotations.Transient;
import org.jdom.Element;
import org.jetbrains.annotations.NonNls;
import org.jetbrains.annotations.NotNull;
@@ -33,6 +36,7 @@ import org.jetbrains.annotations.Nullable;
import java.util.List;
@Tag("module")
public class RunConfigurationModule implements JDOMExternalizable {
private static final Logger LOG = Logger.getInstance(RunConfigurationModule.class);
@@ -40,7 +44,10 @@ public class RunConfigurationModule implements JDOMExternalizable {
@NonNls private static final String ATTRIBUTE = "name";
private Module myModule = null;
@Attribute("name")
private String myModuleName;
private final Project myProject;
public RunConfigurationModule(@NotNull Project project) {
@@ -81,6 +88,7 @@ public class RunConfigurationModule implements JDOMExternalizable {
}
@Nullable
@Transient
public Module getModule() {
if (myModuleName != null) { //caching
myModule = findModule(myModuleName);
@@ -39,4 +39,6 @@ public interface Accessor {
Class<?> getValueClass();
Type getGenericType();
boolean isFinal();
}
@@ -15,6 +15,7 @@
*/
package com.intellij.util.xmlb;
import org.jdom.Element;
import org.jetbrains.annotations.NotNull;
import org.jetbrains.annotations.Nullable;
@@ -41,9 +42,14 @@ class AccessorBindingWrapper implements Binding {
@Nullable
public Object deserialize(Object context, @NotNull Object... nodes) {
Object currentValue = myAccessor.read(context);
Object deserializedValue = myBinding.deserialize(currentValue, nodes);
if (currentValue != deserializedValue) {
myAccessor.write(context, deserializedValue);
if (myBinding instanceof BeanBinding && myAccessor.isFinal()) {
((BeanBinding)myBinding).deserializeInto(currentValue, (Element)nodes[0], null);
}
else {
Object deserializedValue = myBinding.deserialize(currentValue, nodes);
if (currentValue != deserializedValue) {
myAccessor.write(context, deserializedValue);
}
}
return context;
}
@@ -28,6 +28,7 @@ import com.intellij.util.containers.ContainerUtilRt;
import com.intellij.util.containers.MultiMap;
import com.intellij.util.containers.hash.LinkedHashMap;
import com.intellij.util.xmlb.annotations.*;
import gnu.trove.TObjectDoubleHashMap;
import org.jdom.Element;
import org.jetbrains.annotations.NotNull;
import org.jetbrains.annotations.Nullable;
@@ -37,9 +38,8 @@ import java.beans.Introspector;
import java.lang.reflect.Field;
import java.lang.reflect.Method;
import java.lang.reflect.Modifier;
import java.util.*;
import java.util.List;
import java.util.Map;
import java.util.Set;
class BeanBinding implements Binding {
private static final Logger LOG = Logger.getInstance(BeanBinding.class);
@@ -81,23 +81,26 @@ class BeanBinding implements Binding {
@Nullable
public Element serializeInto(@NotNull Object o, @Nullable Element element, @NotNull SerializationFilter filter) {
for (Binding binding : myPropertyBindings.keySet()) {
return serializeInto(o, element, filter, myPropertyBindings.keySet());
}
@Nullable
Element serializeInto(@NotNull Object o, @Nullable Element element, @NotNull SerializationFilter filter, @Nullable Collection<Binding> bindings) {
for (Binding binding : (bindings == null ? myPropertyBindings.keySet() : bindings)) {
Accessor accessor = myPropertyBindings.get(binding);
if (accessor == null) {
LOG.warn("Illegal state: accessor null, " + binding.toString());
continue;
}
if (!filter.accepts(accessor, o)) {
continue;
}
//todo: optimize. Cache it.
Property property = accessor.getAnnotation(Property.class);
if (property != null && property.filter() != SerializationFilter.class) {
try {
if (!ReflectionUtil.newInstance(property.filter()).accepts(accessor, o)) {
continue;
}
}
catch (RuntimeException e) {
throw new XmlSerializationException(e);
}
if (property != null && property.filter() != SerializationFilter.class &&
!ReflectionUtil.newInstance(property.filter()).accepts(accessor, o)) {
continue;
}
if (element == null) {
@@ -130,14 +133,48 @@ class BeanBinding implements Binding {
if (element == null) {
return o;
}
Object instance = XmlSerializerImpl.newInstance(myBeanClass);
Object instance = ReflectionUtil.newInstance(myBeanClass);
deserializeInto(instance, element, null);
return instance;
}
@NotNull
List<Binding> computeOrderedBindings(@NotNull LinkedHashSet<String> accessorNameTracker) {
final TObjectDoubleHashMap<String> weights = new TObjectDoubleHashMap<String>(accessorNameTracker.size());
double weight = 0;
double step = (double)myPropertyBindings.size() / (double)accessorNameTracker.size();
for (String name : accessorNameTracker) {
weights.put(name, weight);
weight += step;
}
weight = 0;
for (Accessor accessor : myPropertyBindings.values()) {
String name = accessor.getName();
if (!weights.containsKey(name)) {
weights.put(name, weight);
}
weight++;
}
Binding[] result = myPropertyBindings.keySet().toArray(new Binding[myPropertyBindings.size()]);
Arrays.sort(result, new Comparator<Binding>() {
@Override
public int compare(@NotNull Binding o1, @NotNull Binding o2) {
String n1 = myPropertyBindings.get(o1).getName();
String n2 = myPropertyBindings.get(o2).getName();
double w1 = weights.get(n1);
double w2 = weights.get(n2);
return (int)(w1 - w2);
}
});
return Arrays.asList(result);
}
public void deserializeInto(@NotNull Object result, @NotNull Element element, @Nullable Set<String> accessorNameTracker) {
Set<Binding> bindings = myPropertyBindings.keySet();
MultiMap<Binding, Object> data = MultiMap.createSmartList();
MultiMap<Binding, Object> data = MultiMap.createLinked();
nextNode:
for (Object child : ContainerUtil.concat(element.getContent(), element.getAttributes())) {
if (XmlSerializerImpl.isIgnoredNode(child)) {
@@ -244,15 +281,25 @@ class BeanBinding implements Binding {
}
}
private static void collectFieldAccessors(Class<?> aClass, List<Accessor> accessors) {
for (Field field : aClass.getFields()) {
final int modifiers = field.getModifiers();
if (Modifier.isPublic(modifiers) && !Modifier.isStatic(modifiers) &&
!Modifier.isFinal(modifiers) && !Modifier.isTransient(modifiers) &&
field.getAnnotation(Transient.class) == null) {
accessors.add(new FieldAccessor(field));
private static void collectFieldAccessors(@NotNull Class<?> aClass, @NotNull List<Accessor> accessors) {
Class<?> currentClass = aClass;
do {
for (Field field : currentClass.getDeclaredFields()) {
int modifiers = field.getModifiers();
if (!Modifier.isStatic(modifiers) &&
(field.getAnnotation(OptionTag.class) != null ||
field.getAnnotation(Tag.class) != null ||
field.getAnnotation(Attribute.class) != null ||
field.getAnnotation(Property.class) != null ||
(Modifier.isPublic(modifiers) &&
!Modifier.isFinal(modifiers) &&
!Modifier.isTransient(modifiers) &&
field.getAnnotation(Transient.class) == null))) {
accessors.add(new FieldAccessor(field));
}
}
}
while ((currentClass = currentClass.getSuperclass()) != null && currentClass.getAnnotation(Transient.class) == null);
}
@Nullable
@@ -21,6 +21,7 @@ import org.jetbrains.annotations.NotNull;
import java.lang.annotation.Annotation;
import java.lang.reflect.Field;
import java.lang.reflect.Modifier;
import java.lang.reflect.Type;
class FieldAccessor implements Accessor {
@@ -81,6 +82,11 @@ class FieldAccessor implements Accessor {
return myField.getGenericType();
}
@Override
public boolean isFinal() {
return Modifier.isFinal(myField.getModifiers());
}
@NonNls
public String toString() {
return "FieldAccessor[" + myField.getDeclaringClass() + "." + myField.getName() + "]";
@@ -16,6 +16,8 @@
package com.intellij.util.xmlb;
import com.intellij.openapi.util.JDOMUtil;
import org.jdom.Attribute;
import org.jdom.Content;
import org.jdom.Text;
import org.jetbrains.annotations.NotNull;
import org.jetbrains.annotations.Nullable;
@@ -44,8 +46,8 @@ class PrimitiveValueBinding implements Binding {
value = JDOMUtil.concatTextNodesValues(nodes);
}
else {
assert nodes[0] != null;
value = JDOMUtil.getValue(nodes[0]);
Object node = nodes[0];
value = node instanceof Attribute ? ((Attribute)node).getValue() : ((Content)node).getValue();
}
return convertString(value);
}
@@ -110,6 +110,11 @@ class PropertyAccessor implements Accessor {
return myGenericType;
}
@Override
public boolean isFinal() {
return false;
}
@NonNls
public String toString() {
return "PropertyAccessor[" + myReadMethod.getDeclaringClass().getName() + "." + getName() +"]";
@@ -36,8 +36,8 @@ public class SkipEmptySerializationFilter extends SerializationFilterBase {
if (Boolean.FALSE.equals(beanValue) ||
(beanValue instanceof String && ((String)beanValue).isEmpty()) ||
(beanValue instanceof Map && ((Map)beanValue).isEmpty()) ||
(beanValue instanceof Collection && ((Collection)beanValue).isEmpty())) {
beanValue instanceof Collection && ((Collection)beanValue).isEmpty() ||
(beanValue instanceof Map && ((Map)beanValue).isEmpty())) {
return false;
}
@@ -16,19 +16,20 @@
package com.intellij.util.xmlb;
import com.intellij.util.ThreeState;
import gnu.trove.THashSet;
import org.jdom.Element;
import org.jetbrains.annotations.NotNull;
import org.jetbrains.annotations.Nullable;
import java.util.Set;
import java.util.LinkedHashSet;
import java.util.List;
public final class SmartSerializer {
private final Set<String> mySerializedAccessorNameTracker;
private final LinkedHashSet<String> mySerializedAccessorNameTracker;
private List<Binding> myOrderedBindings;
private final SerializationFilter mySerializationFilter;
public SmartSerializer(boolean trackSerializedNames, boolean useSkipEmptySerializationFilter) {
mySerializedAccessorNameTracker = trackSerializedNames ? new THashSet<String>() : null;
mySerializedAccessorNameTracker = trackSerializedNames ? new LinkedHashSet<String>() : null;
mySerializationFilter = useSkipEmptySerializationFilter ?
new SkipEmptySerializationFilter() {
@@ -56,10 +57,16 @@ public final class SmartSerializer {
if (mySerializedAccessorNameTracker != null) {
mySerializedAccessorNameTracker.clear();
}
XmlSerializer.deserializeInto(bean, element, mySerializedAccessorNameTracker);
BeanBinding beanBinding = (BeanBinding)XmlSerializerImpl.getBinding(bean.getClass());
beanBinding.deserializeInto(bean, element, mySerializedAccessorNameTracker);
if (mySerializedAccessorNameTracker != null) {
myOrderedBindings = beanBinding.computeOrderedBindings(mySerializedAccessorNameTracker);
}
}
public void writeExternal(@NotNull Object bean, @NotNull Element element) {
XmlSerializer.serializeInto(bean, element, mySerializationFilter);
((BeanBinding)XmlSerializerImpl.getBinding(bean.getClass())).serializeInto(bean, element, mySerializationFilter, myOrderedBindings);
}
}
@@ -16,6 +16,7 @@
package com.intellij.util.xmlb;
import com.intellij.openapi.util.JDOMUtil;
import com.intellij.openapi.util.text.StringUtil;
import com.intellij.util.ArrayUtil;
import com.intellij.util.SmartList;
import com.intellij.util.xmlb.annotations.Tag;
@@ -25,6 +26,7 @@ import org.jdom.Text;
import org.jetbrains.annotations.NotNull;
import org.jetbrains.annotations.Nullable;
import java.util.Collections;
import java.util.List;
class TagBinding extends BasePrimitiveBinding {
@@ -33,7 +35,7 @@ class TagBinding extends BasePrimitiveBinding {
public TagBinding(@NotNull Accessor accessor, @NotNull Tag tagAnnotation) {
super(accessor, tagAnnotation.value(), null);
myTextIfEmpty = tagAnnotation.textIfEmpty();
myTextIfEmpty = StringUtil.nullize(tagAnnotation.textIfEmpty());
}
@Nullable
@@ -57,28 +59,35 @@ class TagBinding extends BasePrimitiveBinding {
@Nullable
public Object deserialize(Object o, @NotNull Object... nodes) {
assert nodes.length > 0;
Object[] children;
List<? extends Content> children;
boolean isBeanBinding = myBinding instanceof BeanBinding;
if (nodes.length == 1) {
children = JDOMUtil.getContent((Element)nodes[0]);
Element node = (Element)nodes[0];
children = isBeanBinding ? node.getChildren() : node.getContent();
}
else {
String name = ((Element)nodes[0]).getName();
List<Content> childrenList = new SmartList<Content>();
children = new SmartList<Content>();
for (Object node : nodes) {
assert ((Element)node).getName().equals(name);
childrenList.addAll(((Element)node).getContent());
Element element = (Element)node;
assert element.getName().equals(name);
//noinspection unchecked
children.addAll(((List)(isBeanBinding ? element.getChildren() : element.getContent())));
}
children = ArrayUtil.toObjectArray(childrenList);
}
if (children.length == 0) {
children = new Object[] {new Text(myTextIfEmpty)};
}
assert myBinding != null;
Object v = myBinding.deserialize(myAccessor.read(o), children);
Object value = XmlSerializerImpl.convert(v, myAccessor.getValueClass());
myAccessor.write(o, value);
if (isBeanBinding && myAccessor.isFinal()) {
((BeanBinding)myBinding).deserializeInto(o, (Element)children.get(0), null);
}
else {
if (children.isEmpty() && myTextIfEmpty != null) {
children = Collections.<Content>singletonList(new Text(myTextIfEmpty));
}
Object v = myBinding.deserialize(myAccessor.read(o), ArrayUtil.toObjectArray(children));
myAccessor.write(o, XmlSerializerImpl.convert(v, myAccessor.getValueClass()));
}
return o;
}
@@ -26,6 +26,7 @@ import java.lang.annotation.Annotation;
import java.lang.ref.SoftReference;
import java.lang.reflect.ParameterizedType;
import java.lang.reflect.Type;
import java.lang.reflect.TypeVariable;
import java.util.*;
import java.util.concurrent.ConcurrentHashMap;
@@ -72,7 +73,18 @@ class XmlSerializerImpl {
}
static Binding getTypeBinding(@NotNull Type type, @Nullable Accessor accessor) {
return _getClassBinding(type instanceof Class ? (Class<?>)type : (Class<?>)((ParameterizedType)type).getRawType(), type, accessor);
Class<?> aClass;
if (type instanceof Class) {
aClass = (Class<?>)type;
}
else if (type instanceof TypeVariable) {
Type bound = ((TypeVariable)type).getBounds()[0];
aClass = bound instanceof Class ? (Class)bound : (Class<?>)((ParameterizedType)bound).getRawType();
}
else {
aClass = (Class<?>)((ParameterizedType)type).getRawType();
}
return _getClassBinding(aClass, type, accessor);
}
private static synchronized Binding _getClassBinding(@NotNull Class<?> aClass, @NotNull Type originalType, @Nullable Accessor accessor) {
@@ -24,9 +24,11 @@ import junit.framework.AssertionFailedError;
import junit.framework.TestCase;
import org.intellij.lang.annotations.Language;
import org.jdom.Element;
import org.jdom.JDOMException;
import org.jetbrains.annotations.NotNull;
import org.jetbrains.annotations.Nullable;
import java.io.IOException;
import java.util.*;
import java.util.concurrent.atomic.AtomicReference;
@@ -1219,6 +1221,37 @@ public class XmlSerializerTest extends TestCase {
doSerializerTest("<BeanWithDefaultAttributeName foo=\"foo\" />", bean);
}
static class Bean2 {
@Attribute
public String ab;
@Attribute
public String module;
@Attribute
public String ac;
}
public void testOrdered() throws IOException, JDOMException {
Bean2 bean = new Bean2();
bean.module = "module";
bean.ab = "ab";
doSerializerTest("<Bean2 ab=\"ab\" module=\"module\" />", bean);
checkSmartSerialization(new Bean2(), "<Bean2 module=\"1\" ab=\"2\" ac=\"32\" />");
checkSmartSerialization(new Bean2(), "<Bean2 ab=\"2\" module=\"1\" ac=\"32\" />");
checkSmartSerialization(new Bean2(), "<Bean2 ac=\"2\" module=\"1\" ab=\"32\" />");
checkSmartSerialization(new Bean2(), "<Bean2 ac=\"2\" ab=\"32\" />");
}
private static void checkSmartSerialization(@NotNull Bean2 bean, @NotNull String serialized) throws IOException, JDOMException {
SmartSerializer serializer = new SmartSerializer();
serializer.readExternal(bean, JDOMUtil.loadDocument(serialized).getRootElement());
Element serializedState = new Element("Bean2");
serializer.writeExternal(bean, serializedState);
assertEquals(serialized, JDOMUtil.writeElement(serializedState));
}
//---------------------------------------------------------------------------------------------------
private static Element assertSerializer(Object bean, String expected, SerializationFilter filter) {
return assertSerializer(bean, expected, "Serialization failure", filter);
@@ -1232,6 +1265,7 @@ public class XmlSerializerTest extends TestCase {
Element element = assertSerializer(bean, expectedText, filter);
//test deserializer
@SuppressWarnings("unchecked")
Class<T> aClass = (Class<T>)bean.getClass();
T o = XmlSerializer.deserialize(element, aClass);
assertSerializer(o, expectedText, "Deserialization failure", filter);