replaced <code></code> with more concise {@code}
[idea/community.git] / plugins / ui-designer / src / com / intellij / uiDesigner / propertyInspector / IntrospectedProperty.java
1 /*
2  * Copyright 2000-2009 JetBrains s.r.o.
3  *
4  * Licensed under the Apache License, Version 2.0 (the "License");
5  * you may not use this file except in compliance with the License.
6  * You may obtain a copy of the License at
7  *
8  * http://www.apache.org/licenses/LICENSE-2.0
9  *
10  * Unless required by applicable law or agreed to in writing, software
11  * distributed under the License is distributed on an "AS IS" BASIS,
12  * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13  * See the License for the specific language governing permissions and
14  * limitations under the License.
15  */
16 package com.intellij.uiDesigner.propertyInspector;
17
18 import com.intellij.openapi.util.Comparing;
19 import com.intellij.psi.*;
20 import com.intellij.psi.search.GlobalSearchScope;
21 import com.intellij.uiDesigner.SwingProperties;
22 import com.intellij.uiDesigner.UIFormXmlConstants;
23 import com.intellij.uiDesigner.XmlWriter;
24 import com.intellij.uiDesigner.radComponents.RadComponent;
25 import com.intellij.uiDesigner.radComponents.RadContainer;
26 import com.intellij.uiDesigner.radComponents.RadGridLayoutManager;
27 import com.intellij.uiDesigner.snapShooter.SnapshotContext;
28 import com.intellij.util.ArrayUtil;
29 import org.jetbrains.annotations.NonNls;
30 import org.jetbrains.annotations.NotNull;
31
32 import javax.swing.*;
33 import java.lang.reflect.Constructor;
34 import java.lang.reflect.InvocationTargetException;
35 import java.lang.reflect.Method;
36
37 /**
38  * @author Anton Katilin
39  * @author Vladimir Kondratyev
40  */
41 public abstract class IntrospectedProperty<V> extends Property<RadComponent, V> {
42   protected final static Object[] EMPTY_OBJECT_ARRAY=new Object[]{};
43
44   /**
45    * This method is used to set property value to "delegee" JComponent
46    */
47   @NotNull protected final Method myReadMethod;
48   /**
49    * This method is used to get property value from "delegee" JComponent
50    */
51   @NotNull private final Method myWriteMethod;
52
53   private final boolean myStoreAsClient;
54
55   @NonNls private static final String INTRO_PREFIX = "Intro:";
56
57   public IntrospectedProperty(final String name,
58                               @NotNull final Method readMethod,
59                               @NotNull final Method writeMethod,
60                               final boolean storeAsClient) {
61     super(null, name);
62     myReadMethod = readMethod;
63     myWriteMethod = writeMethod;
64     myStoreAsClient = storeAsClient;
65   }
66
67   /**
68    * <b>Do not overide this method without serious reason!</b>
69    */
70   public V getValue(final RadComponent component){
71     //noinspection unchecked
72     return (V)invokeGetter(component);
73   }
74
75   protected Object invokeGetter(final RadComponent component) {
76     if (myStoreAsClient) {
77       return component.getClientProperty(INTRO_PREFIX + getName());
78     }
79     try {
80       myReadMethod.setAccessible(true);
81       return myReadMethod.invoke(component.getDelegee(), EMPTY_OBJECT_ARRAY);
82     }
83     catch (Exception e) {
84       throw new RuntimeException(e);
85     }
86   }
87
88   /**
89    * <b>Do not overide this method without serious reason!</b>
90    */
91   protected void setValueImpl(final RadComponent component,final V value) throws Exception{
92     invokeSetter(component, value);
93   }
94
95   protected void invokeSetter(final RadComponent component, final Object value) throws IllegalAccessException, InvocationTargetException {
96     if (myStoreAsClient) {
97       component.putClientProperty(INTRO_PREFIX + getName(), value);
98     }
99     else {
100       myWriteMethod.setAccessible(true);
101       myWriteMethod.invoke(component.getDelegee(), value);
102     }
103   }
104
105   /**
106    * Serializes (writes) propertie's value
107    *
108    * @param value property value which should be serialized.
109    * @param writer writer which should be used for serialization. It is assumed that
110    * before invocation of this method {@code writer} already has opened tag
111    * that corresponds to this property. You can just append some attributes
112    * here or add some subtags.
113    */
114   public void write(@NotNull V value, XmlWriter writer) {
115     writer.addAttribute(UIFormXmlConstants.ATTRIBUTE_VALUE, value.toString());
116   }
117
118   @Override public boolean isModified(final RadComponent component) {
119     return component.isMarkedAsModified(this);
120   }
121
122   @Override public void resetValue(RadComponent component) throws Exception {
123     final V defaultValue = getDefaultValue(component.getDelegee());
124     invokeSetter(component, defaultValue);
125     markTopmostModified(component, false);
126   }
127
128   public void importSnapshotValue(final SnapshotContext context, final JComponent component, final RadComponent radComponent) {
129     try {
130       //noinspection unchecked
131       V value = (V) myReadMethod.invoke(component, EMPTY_OBJECT_ARRAY);
132       V defaultValue = getDefaultValue(radComponent.getDelegee());
133       if (!Comparing.equal(value, defaultValue)) {
134         setValue(radComponent, value);
135       }
136     }
137     catch (Exception e) {
138       // ignore
139     }
140   }
141
142   protected V getDefaultValue(final JComponent delegee) throws Exception {
143     if (myStoreAsClient) {
144       return null;
145     }
146     final Constructor constructor = delegee.getClass().getConstructor(ArrayUtil.EMPTY_CLASS_ARRAY);
147     constructor.setAccessible(true);
148     JComponent newComponent = (JComponent)constructor.newInstance(ArrayUtil.EMPTY_OBJECT_ARRAY);
149     //noinspection unchecked
150     return (V) myReadMethod.invoke(newComponent, EMPTY_OBJECT_ARRAY);
151   }
152
153   @Override
154   public boolean appliesTo(final RadComponent component) {
155     @NonNls String name = getName();
156     //noinspection SimplifiableIfStatement
157     if (name.equals(SwingProperties.PREFERRED_SIZE) ||
158         name.equals(SwingProperties.MINIMUM_SIZE) ||
159         name.equals(SwingProperties.MAXIMUM_SIZE)) {
160       // our own properties must be used instead
161       final RadContainer parent = component.getParent();
162       return parent != null && !(parent.getLayoutManager() instanceof RadGridLayoutManager);
163     }
164
165     // check if property is available in the JDK used by the module containing the component
166     final PsiManager psiManager = PsiManager.getInstance(component.getProject());
167     final GlobalSearchScope scope = component.getModule().getModuleWithDependenciesAndLibrariesScope(true);
168     PsiClass componentClass = JavaPsiFacade.getInstance(psiManager.getProject()).findClass(component.getComponentClassName(), scope);
169     if (componentClass == null) return true;
170     final PsiMethod[] psiMethods = componentClass.findMethodsByName(myReadMethod.getName(), true);
171     for(PsiMethod method: psiMethods) {
172       if (!method.hasModifierProperty(PsiModifier.STATIC) &&
173           method.getParameterList().getParametersCount() == 0) {
174         return true;
175       }
176     }
177     return false;
178   }
179 }