update copyright
[fedora-idea.git] / xml / dom-impl / src / com / intellij / util / xml / impl / StaticGenericInfoBuilder.java
blobb2fb795e70c98c601cec2a9181d2e9fd4d9aafba
1 /*
2 * Copyright 2000-2009 JetBrains s.r.o.
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
8 * http://www.apache.org/licenses/LICENSE-2.0
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.
16 package com.intellij.util.xml.impl;
18 import com.intellij.openapi.util.MultiValuesMap;
19 import com.intellij.openapi.util.Pair;
20 import com.intellij.openapi.util.text.StringUtil;
21 import com.intellij.util.Function;
22 import com.intellij.util.ReflectionCache;
23 import com.intellij.util.ReflectionUtil;
24 import com.intellij.util.SmartList;
25 import com.intellij.util.containers.ContainerUtil;
26 import com.intellij.util.containers.FactoryMap;
27 import com.intellij.util.xml.*;
28 import gnu.trove.THashMap;
29 import gnu.trove.THashSet;
30 import gnu.trove.TIntObjectHashMap;
31 import org.jetbrains.annotations.NonNls;
32 import org.jetbrains.annotations.NotNull;
33 import org.jetbrains.annotations.Nullable;
35 import java.lang.reflect.Method;
36 import java.lang.reflect.Modifier;
37 import java.lang.reflect.Type;
38 import java.util.*;
40 /**
41 * @author peter
43 public class StaticGenericInfoBuilder {
44 private static final Set ADDER_PARAMETER_TYPES = new THashSet<Class>(Arrays.asList(Class.class, int.class));
45 private final Class myClass;
46 private final Type myType;
47 private final MultiValuesMap<XmlName, JavaMethod> myCollectionGetters = new MultiValuesMap<XmlName, JavaMethod>();
48 private final MultiValuesMap<XmlName, JavaMethod> myCollectionAdders = new MultiValuesMap<XmlName, JavaMethod>();
49 private final MultiValuesMap<XmlName, JavaMethod> myCollectionClassAdders = new MultiValuesMap<XmlName, JavaMethod>();
50 private final MultiValuesMap<XmlName, JavaMethod> myCollectionIndexAdders = new MultiValuesMap<XmlName, JavaMethod>();
51 private final MultiValuesMap<XmlName, JavaMethod> myCollectionIndexClassAdders = new MultiValuesMap<XmlName, JavaMethod>();
52 private final MultiValuesMap<XmlName, JavaMethod> myCollectionClassIndexAdders = new MultiValuesMap<XmlName, JavaMethod>();
53 private final Map<XmlName, Type> myCollectionChildrenTypes = new THashMap<XmlName, Type>();
54 private final Map<JavaMethodSignature, String[]> myCompositeCollectionGetters = new THashMap<JavaMethodSignature, String[]>();
55 private final Map<JavaMethodSignature, Pair<String,String[]>> myCompositeCollectionAdders = new THashMap<JavaMethodSignature, Pair<String,String[]>>();
56 private final FactoryMap<XmlName, TIntObjectHashMap<Collection<JavaMethod>>> myFixedChildrenGetters = new FactoryMap<XmlName, TIntObjectHashMap<Collection<JavaMethod>>>() {
57 protected TIntObjectHashMap<Collection<JavaMethod>> create(final XmlName key) {
58 return new TIntObjectHashMap<Collection<JavaMethod>>();
61 private final Map<JavaMethodSignature, AttributeChildDescriptionImpl> myAttributes = new THashMap<JavaMethodSignature, AttributeChildDescriptionImpl>();
63 private boolean myValueElement;
64 private JavaMethod myNameValueGetter;
65 private JavaMethod myCustomChildrenGetter;
67 public StaticGenericInfoBuilder(final DomManagerImpl domManager, final Class aClass, Type type) {
68 myClass = aClass;
69 myType = type;
71 final Set<JavaMethod> methods = new THashSet<JavaMethod>();
72 for (final Method method : ReflectionCache.getMethods(myClass)) {
73 methods.add(JavaMethod.getMethod(myClass, method));
75 for (final JavaMethod method : methods) {
76 if (DomImplUtil.isGetter(method) && method.getAnnotation(NameValue.class) != null) {
77 myNameValueGetter = method;
78 break;
83 final Class implClass = domManager.getImplementation(myClass);
84 if (implClass != null) {
85 for (Method method : ReflectionCache.getMethods(implClass)) {
86 final int modifiers = method.getModifiers();
87 if (!Modifier.isAbstract(modifiers) && !Modifier.isVolatile(modifiers)) {
88 final JavaMethodSignature signature = JavaMethodSignature.getSignature(method);
89 if (signature.findMethod(myClass) != null) {
90 methods.remove(JavaMethod.getMethod(myClass, signature));
97 for (Iterator<JavaMethod> iterator = methods.iterator(); iterator.hasNext();) {
98 final JavaMethod method = iterator.next();
99 if (isCoreMethod(method) || DomImplUtil.isTagValueSetter(method) || method.getAnnotation(PropertyAccessor.class) != null) {
100 iterator.remove();
104 for (Iterator<JavaMethod> iterator = methods.iterator(); iterator.hasNext();) {
105 final JavaMethod method = iterator.next();
106 if (DomImplUtil.isGetter(method) && processGetterMethod(method)) {
107 iterator.remove();
111 for (Iterator<JavaMethod> iterator = methods.iterator(); iterator.hasNext();) {
112 final JavaMethod method = iterator.next();
113 final SubTagsList subTagsList = method.getAnnotation(SubTagsList.class);
114 if (subTagsList != null && method.getName().startsWith("add")) {
115 final String localName = subTagsList.tagName();
116 assert StringUtil.isNotEmpty(localName);
117 final String[] set = subTagsList.value();
118 assert Arrays.asList(set).contains(localName);
119 myCompositeCollectionAdders.put(method.getSignature(), Pair.create(localName, set));
120 iterator.remove();
122 else if (isAddMethod(method)) {
123 final XmlName xmlName = extractTagName(method, "add");
124 if (myCollectionGetters.containsKey(xmlName)) {
125 MultiValuesMap<XmlName, JavaMethod> adders = getAddersMap(method);
126 if (adders != null) {
127 adders.put(xmlName, method);
128 iterator.remove();
134 if (false) {
135 if (!methods.isEmpty()) {
136 StringBuilder sb = new StringBuilder(myClass + " should provide the following implementations:");
137 for (JavaMethod method : methods) {
138 sb.append("\n ");
139 sb.append(method);
141 assert false : sb.toString();
142 //System.out.println(sb.toString());
147 @Nullable
148 private MultiValuesMap<XmlName, JavaMethod> getAddersMap(final JavaMethod method) {
149 final Class<?>[] parameterTypes = method.getParameterTypes();
150 switch (parameterTypes.length) {
151 case 0:
152 return myCollectionAdders;
153 case 1:
154 if (Class.class.equals(parameterTypes[0])) return myCollectionClassAdders;
155 if (isInt(parameterTypes[0])) return myCollectionIndexAdders;
156 break;
157 case 2:
158 if (isIndexClassAdder(parameterTypes[0], parameterTypes[1])) return myCollectionIndexClassAdders;
159 if (isIndexClassAdder(parameterTypes[1], parameterTypes[0])) return myCollectionClassIndexAdders;
161 return null;
164 private static boolean isIndexClassAdder(final Class<?> first, final Class<?> second) {
165 return isInt(first) && second.equals(Class.class);
168 private static boolean isInt(final Class<?> aClass) {
169 return aClass.equals(int.class) || aClass.equals(Integer.class);
172 private static Set<XmlName> getXmlNames(final SubTagsList subTagsList) {
173 return ContainerUtil.map2Set(subTagsList.value(), new Function<String, XmlName>() {
174 public XmlName fun(String s) {
175 return new XmlName(s);
181 private boolean isAddMethod(JavaMethod method) {
182 final XmlName tagName = extractTagName(method, "add");
183 if (tagName == null) return false;
185 final Type type = myCollectionChildrenTypes.get(tagName);
186 if (type == null || !ReflectionUtil.getRawType(type).isAssignableFrom(method.getReturnType())) return false;
188 return ADDER_PARAMETER_TYPES.containsAll(Arrays.asList(method.getParameterTypes()));
191 @Nullable
192 private XmlName extractTagName(JavaMethod method, @NonNls String prefix) {
193 final String name = method.getName();
194 if (!name.startsWith(prefix)) return null;
196 final SubTagList subTagAnnotation = method.getAnnotation(SubTagList.class);
197 if (subTagAnnotation != null && !StringUtil.isEmpty(subTagAnnotation.value())) {
198 return DomImplUtil.createXmlName(subTagAnnotation.value(), method);
201 final String tagName = getNameStrategy(false).convertName(name.substring(prefix.length()));
202 return StringUtil.isEmpty(tagName) ? null : DomImplUtil.createXmlName(tagName, method);
205 private static boolean isDomElement(final Type type) {
206 return type != null && DomElement.class.isAssignableFrom(ReflectionUtil.getRawType(type));
209 private boolean processGetterMethod(final JavaMethod method) {
210 if (DomImplUtil.isTagValueGetter(method)) {
211 myValueElement = true;
212 return true;
215 final Class returnType = method.getReturnType();
216 final boolean isAttributeValueMethod = GenericAttributeValue.class.isAssignableFrom(returnType);
217 final JavaMethodSignature signature = method.getSignature();
218 final Attribute annotation = signature.findAnnotation(Attribute.class, myClass);
219 final boolean isAttributeMethod = annotation != null || isAttributeValueMethod;
220 if (annotation != null) {
221 assert
222 isAttributeValueMethod || GenericAttributeValue.class.isAssignableFrom(returnType) :
223 method + " should return GenericAttributeValue";
225 if (isAttributeMethod) {
226 final String s = annotation == null ? null : annotation.value();
227 String attributeName = StringUtil.isEmpty(s) ? getNameFromMethod(signature, true) : s;
228 assert attributeName != null && StringUtil.isNotEmpty(attributeName) : "Can't guess attribute name from method name: " + method.getName();
229 final XmlName attrName = DomImplUtil.createXmlName(attributeName, method);
230 myAttributes.put(signature, new AttributeChildDescriptionImpl(attrName, method));
231 return true;
234 if (isDomElement(returnType)) {
235 final String qname = getSubTagName(signature);
236 if (qname != null) {
237 final XmlName xmlName = DomImplUtil.createXmlName(qname, method);
238 assert !myCollectionChildrenTypes.containsKey(xmlName) : "Collection and fixed children cannot intersect: " + qname;
239 int index = 0;
240 final SubTag subTagAnnotation = signature.findAnnotation(SubTag.class, myClass);
241 if (subTagAnnotation != null && subTagAnnotation.index() != 0) {
242 index = subTagAnnotation.index();
244 final TIntObjectHashMap<Collection<JavaMethod>> map = myFixedChildrenGetters.get(xmlName);
245 Collection<JavaMethod> methods = map.get(index);
246 if (methods == null) {
247 map.put(index, methods = new SmartList<JavaMethod>());
249 methods.add(method);
250 return true;
254 final Type type = DomReflectionUtil.extractCollectionElementType(method.getGenericReturnType());
255 if (isDomElement(type)) {
256 final CustomChildren customChildren = method.getAnnotation(CustomChildren.class);
257 if (customChildren != null) {
258 myCustomChildrenGetter = method;
259 return true;
262 final SubTagsList subTagsList = method.getAnnotation(SubTagsList.class);
263 if (subTagsList != null) {
264 myCompositeCollectionGetters.put(signature, subTagsList.value());
265 return true;
268 final String qname = getSubTagNameForCollection(signature);
269 if (qname != null) {
270 XmlName xmlName = DomImplUtil.createXmlName(qname, type, method);
271 assert !myFixedChildrenGetters.containsKey(xmlName) : "Collection and fixed children cannot intersect: " + qname;
272 myCollectionChildrenTypes.put(xmlName, type);
273 myCollectionGetters.put(xmlName, method);
274 return true;
278 return false;
281 private static boolean isCoreMethod(final JavaMethod method) {
282 if (method.getSignature().findMethod(DomElement.class) != null) return true;
284 final Class<?> aClass = method.getDeclaringClass();
285 return aClass.equals(GenericAttributeValue.class) || aClass.equals(GenericDomValue.class) && "getConverter".equals(method.getName());
288 @Nullable
289 private String getSubTagName(final JavaMethodSignature method) {
290 final SubTag subTagAnnotation = method.findAnnotation(SubTag.class, myClass);
291 if (subTagAnnotation == null || StringUtil.isEmpty(subTagAnnotation.value())) {
292 return getNameFromMethod(method, false);
294 return subTagAnnotation.value();
297 @Nullable
298 private String getSubTagNameForCollection(final JavaMethodSignature method) {
299 final SubTagList subTagList = method.findAnnotation(SubTagList.class, myClass);
300 if (subTagList == null || StringUtil.isEmpty(subTagList.value())) {
301 final String propertyName = getPropertyName(method);
302 if (propertyName != null) {
303 final String singular = StringUtil.unpluralize(propertyName);
304 assert singular != null : "Can't unpluralize: " + propertyName;
305 return getNameStrategy(false).convertName(singular);
307 else {
308 return null;
311 return subTagList.value();
314 @Nullable
315 private String getNameFromMethod(final JavaMethodSignature method, boolean isAttribute) {
316 final String propertyName = getPropertyName(method);
317 return propertyName == null ? null : getNameStrategy(isAttribute).convertName(propertyName);
320 @Nullable
321 private static String getPropertyName(JavaMethodSignature method) {
322 return StringUtil.getPropertyName(method.getMethodName());
325 @NotNull
326 private DomNameStrategy getNameStrategy(boolean isAttribute) {
327 final DomNameStrategy strategy = DomImplUtil.getDomNameStrategy(ReflectionUtil.getRawType(myClass), isAttribute);
328 return strategy != null ? strategy : DomNameStrategy.HYPHEN_STRATEGY;
331 final JavaMethod getCustomChildrenGetter() {
332 return myCustomChildrenGetter;
335 final Map<JavaMethodSignature, AttributeChildDescriptionImpl> getAttributes() {
336 return myAttributes;
339 final Map<JavaMethodSignature, Pair<FixedChildDescriptionImpl, Integer>> getFixedGetters() {
340 final Map<JavaMethodSignature, Pair<FixedChildDescriptionImpl, Integer>> map = new THashMap<JavaMethodSignature, Pair<FixedChildDescriptionImpl, Integer>>();
341 final Set<XmlName> names = myFixedChildrenGetters.keySet();
342 for (final XmlName name : names) {
343 final TIntObjectHashMap<Collection<JavaMethod>> map1 = myFixedChildrenGetters.get(name);
344 int max = 0;
345 final int[] ints = map1.keys();
346 for (final int i : ints) {
347 max = Math.max(max, i);
349 int count = max + 1;
350 final Collection<JavaMethod>[] getters = new Collection[count];
351 for (final int i : ints) {
352 getters[i] = map1.get(i);
354 final FixedChildDescriptionImpl description = new FixedChildDescriptionImpl(name, map1.get(0).iterator().next().getGenericReturnType(), count, getters);
355 for (int i = 0; i < getters.length; i++) {
356 final Collection<JavaMethod> collection = getters[i];
357 for (final JavaMethod method : collection) {
358 map.put(method.getSignature(), Pair.create(description, i));
362 return map;
365 final Map<JavaMethodSignature, CollectionChildDescriptionImpl> getCollectionGetters() {
366 final Map<JavaMethodSignature, CollectionChildDescriptionImpl> getters = new THashMap<JavaMethodSignature, CollectionChildDescriptionImpl>();
367 for (final XmlName xmlName : myCollectionGetters.keySet()) {
368 final Collection<JavaMethod> collGetters = myCollectionGetters.get(xmlName);
369 final JavaMethod method = collGetters.iterator().next();
372 final CollectionChildDescriptionImpl description = new CollectionChildDescriptionImpl(xmlName, DomReflectionUtil.extractCollectionElementType(method.getGenericReturnType()),
373 myCollectionAdders.get(xmlName),
374 myCollectionClassAdders.get(xmlName),
375 collGetters,
376 myCollectionIndexAdders.get(xmlName),
377 myCollectionIndexClassAdders.get(xmlName),
378 myCollectionClassIndexAdders.get(xmlName));
379 for (final JavaMethod getter : collGetters) {
380 getters.put(getter.getSignature(), description);
384 return getters;
387 final Map<JavaMethodSignature, Pair<String, String[]>> getCompositeCollectionAdders() {
388 return myCompositeCollectionAdders;
391 final Map<JavaMethodSignature, String[]> getCompositeCollectionGetters() {
392 return myCompositeCollectionGetters;
395 public JavaMethod getNameValueGetter() {
396 return myNameValueGetter;
399 public boolean isValueElement() {
400 return myValueElement;