/* * Licensed to the Apache Software Foundation (ASF) under one or more * contributor license agreements. See the NOTICE file distributed with * this work for additional information regarding copyright ownership. * The ASF licenses this file to You 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 org.apache.felix.converter.impl; import java.lang.annotation.Annotation; import java.lang.reflect.Field; import java.lang.reflect.InvocationTargetException; import java.lang.reflect.Method; import java.lang.reflect.Modifier; import java.lang.reflect.ParameterizedType; import java.lang.reflect.Type; import java.util.Arrays; import java.util.Collections; import java.util.HashMap; import java.util.Iterator; import java.util.LinkedHashMap; import java.util.LinkedHashSet; import java.util.Map; import java.util.Map.Entry; import java.util.Set; class Util { private static final Map<Class<?>, Class<?>> boxedClasses; static { Map<Class<?>, Class<?>> m = new HashMap<>(); m.put(int.class, Integer.class); m.put(long.class, Long.class); m.put(double.class, Double.class); m.put(float.class, Float.class); m.put(boolean.class, Boolean.class); m.put(char.class, Character.class); m.put(byte.class, Byte.class); m.put(void.class, Void.class); m.put(short.class, Short.class); boxedClasses = Collections.unmodifiableMap(m); } private Util() {} // prevent instantiation static Type primitiveToBoxed(Type type) { if (type instanceof Class) return primitiveToBoxed((Class<?>) type); else return null; } static Type baseType(Type type) { if (type instanceof Class) return primitiveToBoxed((Class<?>) type); else if (type instanceof ParameterizedType) return type; else return null; } static Class<?> primitiveToBoxed(Class<?> cls) { Class<?> boxed = boxedClasses.get(cls); if (boxed != null) return boxed; else return cls; } static Map<String, Method> getBeanKeys(Class<?> beanClass) { Map<String, Method> keys = new LinkedHashMap<>(); for (Method md : beanClass.getDeclaredMethods()) { String key = getBeanKey(md); if (key != null && !keys.containsKey(key)) { keys.put(key, md); } } return keys; } static String getBeanKey(Method md) { if (Modifier.isStatic(md.getModifiers())) return null; if (!Modifier.isPublic(md.getModifiers())) return null; return getBeanAccessorPropertyName(md); } private static String getBeanAccessorPropertyName(Method md) { if (md.getReturnType().equals(Void.class)) return null; // not an accessor if (md.getParameterTypes().length > 0) return null; // not an accessor if (Object.class.equals(md.getDeclaringClass())) return null; // do not use any methods on the Object class as a accessor String mn = md.getName(); int prefix; if (mn.startsWith("get")) prefix = 3; else if (mn.startsWith("is")) prefix = 2; else return null; // not an accessor prefix if (mn.length() <= prefix) return null; // just 'get' or 'is': not an accessor String propStr = mn.substring(prefix); StringBuilder propName = new StringBuilder(propStr.length()); char firstChar = propStr.charAt(0); if (!Character.isUpperCase(firstChar)) return null; // no acccessor as no camel casing propName.append(Character.toLowerCase(firstChar)); if (propStr.length() > 1) propName.append(propStr.substring(1)); return propName.toString(); } static Map<String, Field> getDTOKeys(Class<?> dto) { Map<String, Field> keys = new LinkedHashMap<>(); for (Field f : dto.getFields()) { String key = getDTOKey(f); if (key != null && !keys.containsKey(key)) keys.put(key, f); } return keys; } static String getDTOKey(Field f) { if (Modifier.isStatic(f.getModifiers())) return null; if (!Modifier.isPublic(f.getModifiers())) return null; return unMangleName(getPrefix(f.getDeclaringClass()), f.getName()); } static Map<String, Set<Method>> getInterfaceKeys(Class<?> intf, Object object) { Map<String, Set<Method>> keys = new LinkedHashMap<>(); String seank = getSingleElementAnnotationKey(intf, object); for (Method md : intf.getMethods()) { String name = getInterfacePropertyName(md, seank, object); if (name != null) { Set<Method> set = keys.get(name); if (set == null) { set = new LinkedHashSet<>(); keys.put(name, set); } md.setAccessible(true); set.add(md); } } for (Iterator<Entry<String, Set<Method>>> it = keys.entrySet().iterator(); it.hasNext(); ) { Entry<String, Set<Method>> entry = it.next(); boolean zeroArgFound = false; for (Method md : entry.getValue()) { if (md.getParameterCount() == 0) { // OK found the zero-arg param zeroArgFound = true; break; } } if (!zeroArgFound) it.remove(); } return keys; } static String getSingleElementAnnotationKey(Class<?> intf, Object obj) { Class<?> ann = getAnnotationType(intf, obj); if (ann == null) return null; boolean valueFound = false; for (Method md : ann.getDeclaredMethods()) { if ("value".equals(md.getName())) { valueFound = true; continue; } if (md.getDefaultValue() == null) { // All elements bar value must have a default return null; } } if (!valueFound) { // Single Element Annotation must have a value element. return null; } return toSingleElementAnnotationKey(ann.getSimpleName()); } private static Class<?> getAnnotationType(Class<?> intf, Object obj) { try { Method md = intf.getMethod("annotationType"); Object res = md.invoke(obj); if (res instanceof Class) return (Class<?>) res; } catch (Exception e) { } return null; } private static String toSingleElementAnnotationKey(String simpleName) { StringBuilder sb = new StringBuilder(); boolean capitalSeen = true; for (char c : simpleName.toCharArray()) { if (!capitalSeen) { if (Character.isUpperCase(c)) { capitalSeen = true; sb.append('.'); } } else { if (Character.isLowerCase(c)) { capitalSeen = false; } } sb.append(Character.toLowerCase(c)); } return sb.toString(); } static String getInterfacePropertyName(Method md, String singleElementAnnotationKey, Object object) { if (md.getReturnType().equals(Void.class)) return null; // not an accessor if (md.getParameterTypes().length > 1) return null; // not an accessor if ("value".equals(md.getName()) && md.getParameterTypes().length == 0 && singleElementAnnotationKey != null) return singleElementAnnotationKey; if (Object.class.equals(md.getDeclaringClass()) || Annotation.class.equals(md.getDeclaringClass())) return null; // do not use any methods on the Object or Annotation class as a accessor if ("annotationType".equals(md.getName()) && md.getParameterTypes().length == 0) { try { Object cls = md.invoke(object); if (cls instanceof Class && ((Class<?>) cls).isAnnotation()) return null; } catch (Exception e) { } } if (md.getDeclaringClass().getSimpleName().startsWith("$Proxy")) { // TODO is there a better way to do this? if (isInheritedMethodInProxy(md, Object.class) || isInheritedMethodInProxy(md, Annotation.class)) return null; } return unMangleName(getPrefix(md.getDeclaringClass()), md.getName()); } private static boolean isInheritedMethodInProxy(Method md, Class<?> cls) { for (Method om : cls.getMethods()) { if (om.getName().equals(md.getName()) && Arrays.equals(om.getParameterTypes(), md.getParameterTypes())) { return true; } } return false; } static Object getInterfaceProperty(Object obj, Method md) throws IllegalAccessException, IllegalArgumentException, InvocationTargetException { if (Modifier.isStatic(md.getModifiers())) return null; if (md.getParameterCount() > 0) return null; return md.invoke(obj); } static String getPrefix(Class<?> cls) { try { Field prefixField = cls.getDeclaredField("PREFIX_"); if (prefixField.getType().equals(String.class)) { if ((prefixField.getModifiers() & (Modifier.PUBLIC | Modifier.FINAL | Modifier.STATIC)) > 0) { return (String) prefixField.get(null); } } } catch (Exception ex) { // LOG no prefix field } if (!cls.isInterface()) { for (Class<?> intf : cls.getInterfaces()) { String prefix = getPrefix(intf); if (prefix.length() > 0) return prefix; } } return ""; } static String mangleName(String prefix, String key) { if (!key.startsWith(prefix)) return null; key = key.substring(prefix.length()); String res = key.replace("_", "__"); res = res.replace("$", "$$"); res = res.replaceAll("[.]([._])", "_\\$$1"); res = res.replace('.', '_'); // TODO handle Java keywords return res; } static String unMangleName(String prefix, String key) { String res = key.replaceAll("_\\$", "."); res = res.replace("__", "\f"); // park double underscore as formfeed char res = res.replace('_', '.'); res = res.replace("$$", "\b"); // park double dollar as backspace char res = res.replace("$", ""); res = res.replace('\f', '_'); // convert formfeed char back to single underscore res = res.replace('\b', '$'); // convert backspace char back go dollar // TODO handle Java keywords return prefix + res; } }