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
;
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
) {
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
;
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) {
104 for (Iterator
<JavaMethod
> iterator
= methods
.iterator(); iterator
.hasNext();) {
105 final JavaMethod method
= iterator
.next();
106 if (DomImplUtil
.isGetter(method
) && processGetterMethod(method
)) {
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
));
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
);
135 if (!methods
.isEmpty()) {
136 StringBuilder sb
= new StringBuilder(myClass
+ " should provide the following implementations:");
137 for (JavaMethod method
: methods
) {
141 assert false : sb
.toString();
142 //System.out.println(sb.toString());
148 private MultiValuesMap
<XmlName
, JavaMethod
> getAddersMap(final JavaMethod method
) {
149 final Class
<?
>[] parameterTypes
= method
.getParameterTypes();
150 switch (parameterTypes
.length
) {
152 return myCollectionAdders
;
154 if (Class
.class.equals(parameterTypes
[0])) return myCollectionClassAdders
;
155 if (isInt(parameterTypes
[0])) return myCollectionIndexAdders
;
158 if (isIndexClassAdder(parameterTypes
[0], parameterTypes
[1])) return myCollectionIndexClassAdders
;
159 if (isIndexClassAdder(parameterTypes
[1], parameterTypes
[0])) return myCollectionClassIndexAdders
;
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()));
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;
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) {
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
));
234 if (isDomElement(returnType
)) {
235 final String qname
= getSubTagName(signature
);
237 final XmlName xmlName
= DomImplUtil
.createXmlName(qname
, method
);
238 assert !myCollectionChildrenTypes
.containsKey(xmlName
) : "Collection and fixed children cannot intersect: " + qname
;
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
>());
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
;
262 final SubTagsList subTagsList
= method
.getAnnotation(SubTagsList
.class);
263 if (subTagsList
!= null) {
264 myCompositeCollectionGetters
.put(signature
, subTagsList
.value());
268 final String qname
= getSubTagNameForCollection(signature
);
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
);
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());
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();
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
);
311 return subTagList
.value();
315 private String
getNameFromMethod(final JavaMethodSignature method
, boolean isAttribute
) {
316 final String propertyName
= getPropertyName(method
);
317 return propertyName
== null ?
null : getNameStrategy(isAttribute
).convertName(propertyName
);
321 private static String
getPropertyName(JavaMethodSignature method
) {
322 return StringUtil
.getPropertyName(method
.getMethodName());
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() {
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
);
345 final int[] ints
= map1
.keys();
346 for (final int i
: ints
) {
347 max
= Math
.max(max
, i
);
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
));
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
),
376 myCollectionIndexAdders
.get(xmlName
),
377 myCollectionIndexClassAdders
.get(xmlName
),
378 myCollectionClassIndexAdders
.get(xmlName
));
379 for (final JavaMethod getter
: collGetters
) {
380 getters
.put(getter
.getSignature(), description
);
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
;