| /* |
| * Copyright 2000-2014 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.xml.impl; |
| |
| import com.intellij.openapi.diagnostic.Logger; |
| import com.intellij.openapi.util.MultiValuesMap; |
| import com.intellij.openapi.util.Pair; |
| import com.intellij.openapi.util.text.StringUtil; |
| import com.intellij.util.ReflectionUtil; |
| import com.intellij.util.SmartList; |
| import com.intellij.util.containers.FactoryMap; |
| import com.intellij.util.xml.*; |
| import gnu.trove.THashMap; |
| import gnu.trove.THashSet; |
| import gnu.trove.TIntObjectHashMap; |
| import org.jetbrains.annotations.NonNls; |
| import org.jetbrains.annotations.NotNull; |
| import org.jetbrains.annotations.Nullable; |
| |
| import java.lang.reflect.Method; |
| import java.lang.reflect.Modifier; |
| import java.lang.reflect.Type; |
| import java.util.*; |
| |
| /** |
| * @author peter |
| */ |
| public class StaticGenericInfoBuilder { |
| private static final Set ADDER_PARAMETER_TYPES = new THashSet<Class>(Arrays.asList(Class.class, int.class)); |
| private static final Logger LOG = Logger.getInstance(StaticGenericInfoBuilder.class); |
| private final Class myClass; |
| private final MultiValuesMap<XmlName, JavaMethod> myCollectionGetters = new MultiValuesMap<XmlName, JavaMethod>(); |
| final MultiValuesMap<XmlName, JavaMethod> collectionAdders = new MultiValuesMap<XmlName, JavaMethod>(); |
| final MultiValuesMap<XmlName, JavaMethod> collectionClassAdders = new MultiValuesMap<XmlName, JavaMethod>(); |
| final MultiValuesMap<XmlName, JavaMethod> collectionIndexAdders = new MultiValuesMap<XmlName, JavaMethod>(); |
| final MultiValuesMap<XmlName, JavaMethod> collectionIndexClassAdders = new MultiValuesMap<XmlName, JavaMethod>(); |
| final MultiValuesMap<XmlName, JavaMethod> collectionClassIndexAdders = new MultiValuesMap<XmlName, JavaMethod>(); |
| private final Map<XmlName, Type> myCollectionChildrenTypes = new THashMap<XmlName, Type>(); |
| private final Map<JavaMethodSignature, String[]> myCompositeCollectionGetters = new THashMap<JavaMethodSignature, String[]>(); |
| private final Map<JavaMethodSignature, Pair<String,String[]>> myCompositeCollectionAdders = new THashMap<JavaMethodSignature, Pair<String,String[]>>(); |
| private final FactoryMap<XmlName, TIntObjectHashMap<Collection<JavaMethod>>> myFixedChildrenGetters = new FactoryMap<XmlName, TIntObjectHashMap<Collection<JavaMethod>>>() { |
| @Override |
| protected TIntObjectHashMap<Collection<JavaMethod>> create(final XmlName key) { |
| return new TIntObjectHashMap<Collection<JavaMethod>>(); |
| } |
| }; |
| private final Map<JavaMethodSignature, AttributeChildDescriptionImpl> myAttributes = new THashMap<JavaMethodSignature, AttributeChildDescriptionImpl>(); |
| |
| private boolean myValueElement; |
| private JavaMethod myNameValueGetter; |
| private JavaMethod myCustomChildrenGetter; |
| |
| public StaticGenericInfoBuilder(final Class aClass) { |
| myClass = aClass; |
| |
| final Set<JavaMethod> methods = new LinkedHashSet<JavaMethod>(); |
| InvocationCache invocationCache = DomApplicationComponent.getInstance().getInvocationCache(myClass); |
| for (final Method method : ReflectionUtil.getClassPublicMethods(myClass)) { |
| methods.add(invocationCache.getInternedMethod(method)); |
| } |
| for (final JavaMethod method : methods) { |
| if (DomImplUtil.isGetter(method) && method.getAnnotation(NameValue.class) != null) { |
| myNameValueGetter = method; |
| break; |
| } |
| } |
| |
| { |
| final Class implClass = DomApplicationComponent.getInstance().getImplementation(myClass); |
| if (implClass != null) { |
| for (Method method : ReflectionUtil.getClassPublicMethods(implClass)) { |
| final int modifiers = method.getModifiers(); |
| if (!Modifier.isAbstract(modifiers) && |
| !Modifier.isVolatile(modifiers) && |
| new JavaMethodSignature(method).findMethod(myClass) != null) { |
| methods.remove(invocationCache.getInternedMethod(method)); |
| } |
| } |
| } |
| } |
| |
| for (Iterator<JavaMethod> iterator = methods.iterator(); iterator.hasNext();) { |
| final JavaMethod method = iterator.next(); |
| if (isCoreMethod(method) || DomImplUtil.isTagValueSetter(method) || method.getAnnotation(PropertyAccessor.class) != null) { |
| iterator.remove(); |
| } |
| } |
| |
| for (Iterator<JavaMethod> iterator = methods.iterator(); iterator.hasNext();) { |
| final JavaMethod method = iterator.next(); |
| if (DomImplUtil.isGetter(method) && processGetterMethod(method)) { |
| iterator.remove(); |
| } |
| } |
| |
| for (Iterator<JavaMethod> iterator = methods.iterator(); iterator.hasNext();) { |
| final JavaMethod method = iterator.next(); |
| final SubTagsList subTagsList = method.getAnnotation(SubTagsList.class); |
| if (subTagsList != null && method.getName().startsWith("add")) { |
| final String localName = subTagsList.tagName(); |
| assert StringUtil.isNotEmpty(localName); |
| final String[] set = subTagsList.value(); |
| assert Arrays.asList(set).contains(localName); |
| myCompositeCollectionAdders.put(method.getSignature(), Pair.create(localName, set)); |
| iterator.remove(); |
| } |
| else if (isAddMethod(method)) { |
| final XmlName xmlName = extractTagName(method, "add"); |
| if (myCollectionGetters.containsKey(xmlName)) { |
| MultiValuesMap<XmlName, JavaMethod> adders = getAddersMap(method); |
| if (adders != null) { |
| adders.put(xmlName, method); |
| iterator.remove(); |
| } |
| } |
| } |
| } |
| |
| //noinspection ConstantIfStatement |
| if (false) { |
| if (!methods.isEmpty()) { |
| StringBuilder sb = new StringBuilder(myClass + " should provide the following implementations:"); |
| for (JavaMethod method : methods) { |
| sb.append("\n "); |
| sb.append(method); |
| } |
| assert false : sb.toString(); |
| //System.out.println(sb.toString()); |
| } |
| } |
| } |
| |
| @Nullable |
| private MultiValuesMap<XmlName, JavaMethod> getAddersMap(final JavaMethod method) { |
| final Class<?>[] parameterTypes = method.getParameterTypes(); |
| switch (parameterTypes.length) { |
| case 0: |
| return collectionAdders; |
| case 1: |
| if (Class.class.equals(parameterTypes[0])) return collectionClassAdders; |
| if (isInt(parameterTypes[0])) return collectionIndexAdders; |
| break; |
| case 2: |
| if (isIndexClassAdder(parameterTypes[0], parameterTypes[1])) return collectionIndexClassAdders; |
| if (isIndexClassAdder(parameterTypes[1], parameterTypes[0])) return collectionClassIndexAdders; |
| } |
| return null; |
| } |
| |
| private static boolean isIndexClassAdder(final Class<?> first, final Class<?> second) { |
| return isInt(first) && second.equals(Class.class); |
| } |
| |
| private static boolean isInt(final Class<?> aClass) { |
| return aClass.equals(int.class) || aClass.equals(Integer.class); |
| } |
| |
| private boolean isAddMethod(JavaMethod method) { |
| final XmlName tagName = extractTagName(method, "add"); |
| if (tagName == null) return false; |
| |
| final Type type = myCollectionChildrenTypes.get(tagName); |
| if (type == null || !ReflectionUtil.getRawType(type).isAssignableFrom(method.getReturnType())) return false; |
| |
| return ADDER_PARAMETER_TYPES.containsAll(Arrays.asList(method.getParameterTypes())); |
| } |
| |
| @Nullable |
| private XmlName extractTagName(JavaMethod method, @NonNls String prefix) { |
| final String name = method.getName(); |
| if (!name.startsWith(prefix)) return null; |
| |
| final SubTagList subTagAnnotation = method.getAnnotation(SubTagList.class); |
| if (subTagAnnotation != null && !StringUtil.isEmpty(subTagAnnotation.value())) { |
| return DomImplUtil.createXmlName(subTagAnnotation.value(), method); |
| } |
| |
| final String tagName = getNameStrategy(false).convertName(name.substring(prefix.length())); |
| return StringUtil.isEmpty(tagName) ? null : DomImplUtil.createXmlName(tagName, method); |
| } |
| |
| private static boolean isDomElement(final Type type) { |
| return type != null && DomElement.class.isAssignableFrom(ReflectionUtil.getRawType(type)); |
| } |
| |
| private boolean processGetterMethod(final JavaMethod method) { |
| if (DomImplUtil.isTagValueGetter(method)) { |
| myValueElement = true; |
| return true; |
| } |
| |
| final Class returnType = method.getReturnType(); |
| final boolean isAttributeValueMethod = GenericAttributeValue.class.isAssignableFrom(returnType); |
| final JavaMethodSignature signature = method.getSignature(); |
| final Attribute annotation = method.getAnnotation(Attribute.class); |
| final boolean isAttributeMethod = annotation != null || isAttributeValueMethod; |
| if (annotation != null) { |
| assert |
| isAttributeValueMethod || GenericAttributeValue.class.isAssignableFrom(returnType) : |
| method + " should return GenericAttributeValue"; |
| } |
| if (isAttributeMethod) { |
| final String s = annotation == null ? null : annotation.value(); |
| String attributeName = StringUtil.isEmpty(s) ? getNameFromMethod(method, true) : s; |
| assert attributeName != null && StringUtil.isNotEmpty(attributeName) : "Can't guess attribute name from method name: " + method.getName(); |
| final XmlName attrName = DomImplUtil.createXmlName(attributeName, method); |
| myAttributes.put(signature, new AttributeChildDescriptionImpl(attrName, method)); |
| return true; |
| } |
| |
| if (isDomElement(returnType)) { |
| final String qname = getSubTagName(method); |
| if (qname != null) { |
| final XmlName xmlName = DomImplUtil.createXmlName(qname, method); |
| Type collectionType = myCollectionChildrenTypes.get(xmlName); |
| if (collectionType != null) { |
| LOG.error("Collection (" + collectionType + ") and fixed children cannot intersect: " + qname + " for " + myClass); |
| } |
| int index = 0; |
| final SubTag subTagAnnotation = method.getAnnotation(SubTag.class); |
| if (subTagAnnotation != null && subTagAnnotation.index() != 0) { |
| index = subTagAnnotation.index(); |
| } |
| final TIntObjectHashMap<Collection<JavaMethod>> map = myFixedChildrenGetters.get(xmlName); |
| Collection<JavaMethod> methods = map.get(index); |
| if (methods == null) { |
| map.put(index, methods = new SmartList<JavaMethod>()); |
| } |
| methods.add(method); |
| return true; |
| } |
| } |
| |
| final Type type = DomReflectionUtil.extractCollectionElementType(method.getGenericReturnType()); |
| if (isDomElement(type)) { |
| final CustomChildren customChildren = method.getAnnotation(CustomChildren.class); |
| if (customChildren != null) { |
| myCustomChildrenGetter = method; |
| return true; |
| } |
| |
| final SubTagsList subTagsList = method.getAnnotation(SubTagsList.class); |
| if (subTagsList != null) { |
| myCompositeCollectionGetters.put(signature, subTagsList.value()); |
| return true; |
| } |
| |
| final String qname = getSubTagNameForCollection(method); |
| if (qname != null) { |
| XmlName xmlName = DomImplUtil.createXmlName(qname, type, method); |
| assert !myFixedChildrenGetters.containsKey(xmlName) : "Collection and fixed children cannot intersect: " + qname; |
| myCollectionChildrenTypes.put(xmlName, type); |
| myCollectionGetters.put(xmlName, method); |
| return true; |
| } |
| } |
| |
| return false; |
| } |
| |
| private static boolean isCoreMethod(final JavaMethod method) { |
| if (method.getSignature().findMethod(DomElement.class) != null) return true; |
| |
| final Class<?> aClass = method.getDeclaringClass(); |
| return aClass.equals(GenericAttributeValue.class) || aClass.equals(GenericDomValue.class) && "getConverter".equals(method.getName()); |
| } |
| |
| @Nullable |
| private String getSubTagName(final JavaMethod method) { |
| final SubTag subTagAnnotation = method.getAnnotation(SubTag.class); |
| if (subTagAnnotation == null || StringUtil.isEmpty(subTagAnnotation.value())) { |
| return getNameFromMethod(method, false); |
| } |
| return subTagAnnotation.value(); |
| } |
| |
| @Nullable |
| private String getSubTagNameForCollection(final JavaMethod method) { |
| final SubTagList subTagList = method.getAnnotation(SubTagList.class); |
| if (subTagList == null || StringUtil.isEmpty(subTagList.value())) { |
| final String propertyName = getPropertyName(method); |
| if (propertyName != null) { |
| final String singular = StringUtil.unpluralize(propertyName); |
| assert singular != null : "Can't unpluralize: " + propertyName; |
| return getNameStrategy(false).convertName(singular); |
| } |
| else { |
| return null; |
| } |
| } |
| return subTagList.value(); |
| } |
| |
| @Nullable |
| private String getNameFromMethod(final JavaMethod method, boolean isAttribute) { |
| final String propertyName = getPropertyName(method); |
| return propertyName == null ? null : getNameStrategy(isAttribute).convertName(propertyName); |
| } |
| |
| @Nullable |
| private static String getPropertyName(JavaMethod method) { |
| return StringUtil.getPropertyName(method.getMethodName()); |
| } |
| |
| @NotNull |
| private DomNameStrategy getNameStrategy(boolean isAttribute) { |
| final DomNameStrategy strategy = DomImplUtil.getDomNameStrategy(ReflectionUtil.getRawType(myClass), isAttribute); |
| return strategy != null ? strategy : DomNameStrategy.HYPHEN_STRATEGY; |
| } |
| |
| final JavaMethod getCustomChildrenGetter() { |
| return myCustomChildrenGetter; |
| } |
| |
| final Map<JavaMethodSignature, AttributeChildDescriptionImpl> getAttributes() { |
| return myAttributes; |
| } |
| |
| final Map<JavaMethodSignature, Pair<FixedChildDescriptionImpl, Integer>> getFixedGetters() { |
| final Map<JavaMethodSignature, Pair<FixedChildDescriptionImpl, Integer>> map = new THashMap<JavaMethodSignature, Pair<FixedChildDescriptionImpl, Integer>>(); |
| final Set<XmlName> names = myFixedChildrenGetters.keySet(); |
| for (final XmlName name : names) { |
| final TIntObjectHashMap<Collection<JavaMethod>> map1 = myFixedChildrenGetters.get(name); |
| int max = 0; |
| final int[] ints = map1.keys(); |
| for (final int i : ints) { |
| max = Math.max(max, i); |
| } |
| int count = max + 1; |
| final Collection<JavaMethod>[] getters = new Collection[count]; |
| for (final int i : ints) { |
| getters[i] = map1.get(i); |
| } |
| final FixedChildDescriptionImpl description = new FixedChildDescriptionImpl(name, map1.get(0).iterator().next().getGenericReturnType(), count, getters); |
| for (int i = 0; i < getters.length; i++) { |
| final Collection<JavaMethod> collection = getters[i]; |
| for (final JavaMethod method : collection) { |
| map.put(method.getSignature(), Pair.create(description, i)); |
| } |
| } |
| } |
| return map; |
| } |
| |
| final Map<JavaMethodSignature, CollectionChildDescriptionImpl> getCollectionGetters() { |
| final Map<JavaMethodSignature, CollectionChildDescriptionImpl> getters = new THashMap<JavaMethodSignature, CollectionChildDescriptionImpl>(); |
| for (final XmlName xmlName : myCollectionGetters.keySet()) { |
| final Collection<JavaMethod> collGetters = myCollectionGetters.get(xmlName); |
| final JavaMethod method = collGetters.iterator().next(); |
| |
| |
| final CollectionChildDescriptionImpl description = new CollectionChildDescriptionImpl(xmlName, DomReflectionUtil.extractCollectionElementType(method.getGenericReturnType()), |
| collGetters |
| ); |
| for (final JavaMethod getter : collGetters) { |
| getters.put(getter.getSignature(), description); |
| } |
| } |
| |
| return getters; |
| } |
| |
| final Map<JavaMethodSignature, Pair<String, String[]>> getCompositeCollectionAdders() { |
| return myCompositeCollectionAdders; |
| } |
| |
| final Map<JavaMethodSignature, String[]> getCompositeCollectionGetters() { |
| return myCompositeCollectionGetters; |
| } |
| |
| public JavaMethod getNameValueGetter() { |
| return myNameValueGetter; |
| } |
| |
| public boolean isValueElement() { |
| return myValueElement; |
| } |
| } |