001/* 002 * Licensed to the Apache Software Foundation (ASF) under one or more 003 * contributor license agreements. See the NOTICE file distributed with 004 * this work for additional information regarding copyright ownership. 005 * The ASF licenses this file to You under the Apache License, Version 2.0 006 * (the "License"); you may not use this file except in compliance with 007 * the License. You may obtain a copy of the License at 008 * 009 * http://www.apache.org/licenses/LICENSE-2.0 010 * 011 * Unless required by applicable law or agreed to in writing, software 012 * distributed under the License is distributed on an "AS IS" BASIS, 013 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 014 * See the License for the specific language governing permissions and 015 * limitations under the License. 016 */ 017 018package org.apache.commons.proxy2.impl; 019 020import java.io.Serializable; 021import java.lang.reflect.Array; 022import java.lang.reflect.Method; 023import java.text.ParsePosition; 024import java.util.ArrayList; 025import java.util.Collections; 026import java.util.HashMap; 027import java.util.List; 028import java.util.Map; 029 030import org.apache.commons.lang3.ArrayUtils; 031import org.apache.commons.lang3.StringUtils; 032import org.apache.commons.lang3.Validate; 033import org.apache.commons.lang3.builder.HashCodeBuilder; 034import org.apache.commons.lang3.reflect.MethodUtils; 035import org.apache.commons.lang3.tuple.Pair; 036 037/** 038 * A class for capturing the signature of a method (its name and parameter types). 039 * 040 * @since 2.0 041 */ 042public class MethodSignature implements Serializable 043{ 044 private static final long serialVersionUID = 1L; 045 046 private static final Map<Class<?>, Character> PRIMITIVE_ABBREVIATIONS; 047 private static final Map<Character, Class<?>> REVERSE_ABBREVIATIONS; 048 static 049 { 050 final Map<Class<?>, Character> primitiveAbbreviations = new HashMap<Class<?>, Character>(); 051 primitiveAbbreviations.put(Boolean.TYPE, Character.valueOf('Z')); 052 primitiveAbbreviations.put(Byte.TYPE, Character.valueOf('B')); 053 primitiveAbbreviations.put(Short.TYPE, Character.valueOf('S')); 054 primitiveAbbreviations.put(Integer.TYPE, Character.valueOf('I')); 055 primitiveAbbreviations.put(Character.TYPE, Character.valueOf('C')); 056 primitiveAbbreviations.put(Long.TYPE, Character.valueOf('J')); 057 primitiveAbbreviations.put(Float.TYPE, Character.valueOf('F')); 058 primitiveAbbreviations.put(Double.TYPE, Character.valueOf('D')); 059 primitiveAbbreviations.put(Void.TYPE, Character.valueOf('V')); 060 final Map<Character, Class<?>> reverseAbbreviations = new HashMap<Character, Class<?>>(); 061 for (Map.Entry<Class<?>, Character> e : primitiveAbbreviations.entrySet()) 062 { 063 reverseAbbreviations.put(e.getValue(), e.getKey()); 064 } 065 PRIMITIVE_ABBREVIATIONS = Collections.unmodifiableMap(primitiveAbbreviations); 066 REVERSE_ABBREVIATIONS = Collections.unmodifiableMap(reverseAbbreviations); 067 } 068 069 private static void appendTo(StringBuilder buf, Class<?> type) 070 { 071 if (type.isPrimitive()) 072 { 073 buf.append(PRIMITIVE_ABBREVIATIONS.get(type)); 074 } 075 else if (type.isArray()) 076 { 077 buf.append('['); 078 appendTo(buf, type.getComponentType()); 079 } 080 else 081 { 082 buf.append('L').append(type.getName().replace('.', '/')).append(';'); 083 } 084 } 085 086 private static class SignaturePosition extends ParsePosition 087 { 088 SignaturePosition() 089 { 090 super(0); 091 } 092 093 SignaturePosition next() 094 { 095 return plus(1); 096 } 097 098 SignaturePosition plus(int addend) 099 { 100 setIndex(getIndex() + addend); 101 return this; 102 } 103 } 104 105 private static Pair<String, Class<?>[]> parse(String internal) 106 { 107 Validate.notBlank(internal, "Cannot parse blank method signature"); 108 final SignaturePosition pos = new SignaturePosition(); 109 int lparen = internal.indexOf('(', pos.getIndex()); 110 Validate.isTrue(lparen > 0, "Method signature \"%s\" requires parentheses", internal); 111 final String name = internal.substring(0, lparen).trim(); 112 Validate.notBlank(name, "Method signature \"%s\" has blank name", internal); 113 114 pos.setIndex(lparen + 1); 115 116 boolean complete = false; 117 final List<Class<?>> params = new ArrayList<Class<?>>(); 118 while (pos.getIndex() < internal.length()) 119 { 120 final char c = internal.charAt(pos.getIndex()); 121 if (Character.isWhitespace(c)) 122 { 123 pos.next(); 124 continue; 125 } 126 final Character k = Character.valueOf(c); 127 if (REVERSE_ABBREVIATIONS.containsKey(k)) 128 { 129 params.add(REVERSE_ABBREVIATIONS.get(k)); 130 pos.next(); 131 continue; 132 } 133 if (')' == c) 134 { 135 complete = true; 136 pos.next(); 137 break; 138 } 139 try 140 { 141 params.add(parseType(internal, pos)); 142 } 143 catch (ClassNotFoundException e) 144 { 145 throw new IllegalArgumentException(String.format("Method signature \"%s\" references unknown type", 146 internal), e); 147 } 148 } 149 Validate.isTrue(complete, "Method signature \"%s\" is incomplete", internal); 150 Validate.isTrue(StringUtils.isBlank(internal.substring(pos.getIndex())), 151 "Method signature \"%s\" includes unrecognized content beyond end", internal); 152 153 return Pair.of(name, params.toArray(ArrayUtils.EMPTY_CLASS_ARRAY)); 154 } 155 156 private static Class<?> parseType(String internal, SignaturePosition pos) throws ClassNotFoundException 157 { 158 final int here = pos.getIndex(); 159 final char c = internal.charAt(here); 160 161 switch (c) 162 { 163 case '[': 164 pos.next(); 165 final Class<?> componentType = parseType(internal, pos); 166 return Array.newInstance(componentType, 0).getClass(); 167 case 'L': 168 pos.next(); 169 final int type = pos.getIndex(); 170 final int semi = internal.indexOf(';', type); 171 Validate.isTrue(semi > 0, "Type at index %d of method signature \"%s\" not terminated by semicolon", 172 Integer.valueOf(here), internal); 173 final String className = internal.substring(type, semi).replace('/', '.'); 174 Validate.notBlank(className, "Invalid classname at position %d of method signature \"%s\"", 175 Integer.valueOf(type), internal); 176 pos.setIndex(semi + 1); 177 return Class.forName(className); 178 default: 179 throw new IllegalArgumentException(String.format( 180 "Unexpected character at index %d of method signature \"%s\"", 181 Integer.valueOf(here), internal)); 182 } 183 } 184 185 //****************************************************************************************************************** 186 // Fields 187 //****************************************************************************************************************** 188 189 /** 190 * Stored as a Java method descriptor minus return type. 191 */ 192 private final String internal; 193 194 //****************************************************************************************************************** 195 // Constructors 196 //****************************************************************************************************************** 197 198 /** 199 * Create a new MethodSignature instance. 200 * 201 * @param method 202 */ 203 public MethodSignature(Method method) 204 { 205 final StringBuilder buf = new StringBuilder(method.getName()).append('('); 206 for (Class<?> p : method.getParameterTypes()) 207 { 208 appendTo(buf, p); 209 } 210 buf.append(')'); 211 this.internal = buf.toString(); 212 } 213 214 //****************************************************************************************************************** 215 // Methods 216 //****************************************************************************************************************** 217 218 /** 219 * Get the corresponding {@link Method} instance from the specified {@link Class}. 220 * 221 * @param type 222 * @return Method 223 */ 224 public Method toMethod(Class<?> type) 225 { 226 final Pair<String, Class<?>[]> info = parse(internal); 227 return MethodUtils.getAccessibleMethod(type, info.getLeft(), info.getRight()); 228 } 229 230 //****************************************************************************************************************** 231 // Canonical Methods 232 //****************************************************************************************************************** 233 234 /** 235 * {@inheritDoc} 236 */ 237 @Override 238 public boolean equals(Object o) 239 { 240 if (o == null) 241 { 242 return false; 243 } 244 if (o == this) 245 { 246 return true; 247 } 248 if (o.getClass() != getClass()) 249 { 250 return false; 251 } 252 MethodSignature other = (MethodSignature) o; 253 return other.internal.equals(internal); 254 } 255 256 /** 257 * {@inheritDoc} 258 */ 259 @Override 260 public int hashCode() 261 { 262 return new HashCodeBuilder().append(internal).build().intValue(); 263 } 264 265 /** 266 * {@inheritDoc} 267 */ 268 @Override 269 public String toString() 270 { 271 return internal; 272 } 273}