001/*
002 *                    BioJava development code
003 *
004 * This code may be freely distributed and modified under the
005 * terms of the GNU Lesser General Public Licence.  This should
006 * be distributed with the code.  If you do not have a copy,
007 * see:
008 *
009 *      http://www.gnu.org/copyleft/lesser.html
010 *
011 * Copyright for this code is held jointly by the individual
012 * authors.  These should be listed in @author doc comments.
013 *
014 * For more information on the BioJava project and its aims,
015 * or to join the biojava-l mailing list, visit the home page
016 * at:
017 *
018 *      http://www.biojava.org/
019 *
020 */
021package org.biojava.utils.bytecode;
022
023import java.util.*;
024import java.lang.reflect.*;
025
026// fix for array types so that their descriptors are not prefixed with L
027
028/**
029 * CodeClass instances that represent normal Java Class objects.
030 *
031 * <p>
032 * Instances of IntrospectedCodeClass are generated using the static factory
033 * methods named forClass(). These methods ensure that the same
034 * IntrospectedCodeClass instance is returned for multiple invocations with
035 * the same argument.
036 * </p>
037 *
038 * @author Thomas Down
039 * @author Matthew Pocock
040 */
041public class IntrospectedCodeClass implements CodeClass {
042  private static Map introspectedClasses;
043  private static Map primitiveDescriptors;
044
045  static {
046    introspectedClasses = new HashMap();
047
048    primitiveDescriptors = new HashMap();
049    primitiveDescriptors.put(Byte.TYPE, "B");
050    primitiveDescriptors.put(Character.TYPE, "C");
051    primitiveDescriptors.put(Double.TYPE, "D");
052    primitiveDescriptors.put(Float.TYPE, "F");
053    primitiveDescriptors.put(Integer.TYPE, "I");
054    primitiveDescriptors.put(Long.TYPE, "J");
055    primitiveDescriptors.put(Short.TYPE, "S");
056    primitiveDescriptors.put(Boolean.TYPE, "Z");
057    primitiveDescriptors.put(Void.TYPE, "V");
058
059  }
060
061  /**
062   * Get the CodeClass for a Java Class.
063   *
064   * @param c  the Java Class to reflect
065   * @return  a CodeClass representing the class
066   */
067  public static CodeClass forClass(Class c) {
068    CodeClass cc = (CodeClass) introspectedClasses.get(c);
069    if (cc == null) {
070      cc = new IntrospectedCodeClass(c);
071      introspectedClasses.put(c, cc);
072    }
073    return cc;
074  }
075
076  /**
077   * Get the CodeClass for a Java class name.
078   *
079   * @param name  the Java class name to reflect
080   * @return  a CodeClass representing the class
081   */
082  public static CodeClass forClass(String name) throws ClassNotFoundException {
083    Class c = ClassLoader.getSystemClassLoader().loadClass(name);
084
085    return forClass(c);
086  }
087
088  public static CodeMethod forMethod(Method method) {
089    return new IntrospectedCodeMethod(method);
090  }
091
092  //
093  // Instance
094  //
095
096  private Class clazz;
097
098  private IntrospectedCodeClass(Class c) {
099    this.clazz = c;
100  }
101
102  public String getName() {
103    return clazz.getName();
104  }
105
106  public String getJName() {
107    String name = getName();
108    StringBuffer sb = new StringBuffer();
109    for (int i = 0; i < name.length(); ++i) {
110      char c = name.charAt(i);
111      if (c == '.')
112        sb.append('/');
113      else
114        sb.append(c);
115    }
116
117    return sb.toString();
118  }
119
120  public String getDescriptor() {
121    if (clazz.isPrimitive()) {
122      String desc = (String) primitiveDescriptors.get(clazz);
123      if (desc == null) {
124        throw new RuntimeException("Unknown primitive type " + clazz.getName() + ", eeek!");
125      }
126      return desc;
127    }
128
129    if (clazz.isArray()) {
130      return "[" + IntrospectedCodeClass.forClass(clazz.getComponentType()).getDescriptor();
131    } else {
132      String name = getName();
133      StringBuffer sb = new StringBuffer();
134      sb.append('L');
135      for (int i = 0; i < name.length(); ++i) {
136        char c = name.charAt(i);
137        if (c == '.') {
138          sb.append('/');
139        } else {
140          sb.append(c);
141        }
142      }
143      sb.append(';');
144      return sb.toString();
145    }
146  }
147
148  public int getModifiers() {
149    return clazz.getModifiers();
150  }
151
152  public CodeClass getSuperClass() {
153    return IntrospectedCodeClass.forClass(clazz.getSuperclass());
154  }
155
156  public List getInterfaces() {
157    Class[] interfaces = clazz.getInterfaces();
158    return Arrays.asList(interfaces);
159  }
160
161  private Set _methods;
162  private Map _methsByNameSig;
163  private Map _methsByName;
164
165  public Set getMethods() {
166    initMethods();
167    return _methods;
168  }
169
170  private void initMethods() {
171    if (_methods == null) {
172      Map meths = new HashMap();
173      popMeths(this.clazz, meths);
174      popIMeths(this.clazz, meths);
175      _methods = new HashSet(meths.values());
176      _methsByNameSig = new HashMap();
177      _methsByName = new HashMap();
178      for(Iterator i = _methods.iterator(); i.hasNext(); ) {
179        CodeMethod m = (CodeMethod) i.next();
180        Set mbn = (Set) _methsByName.get(m.getName());
181        if(mbn == null) {
182          _methsByName.put(m.getName(), mbn = new HashSet());
183        }
184        mbn.add(m);
185        _methsByNameSig.put(makeNameSig(m), m);
186      }
187    }
188  }
189
190  private void popMeths(Class clazz, Map meths) {
191    Method[] methods = clazz.getDeclaredMethods();
192    for(int i = 0; i < methods.length; i++) {
193      Method m = methods[i];
194      ArrayList sigList = new ArrayList();
195      sigList.add(m.getName());
196      sigList.addAll(Arrays.asList(m.getParameterTypes()));
197      if(!meths.containsKey(sigList)) {
198        meths.put(sigList, new IntrospectedCodeMethod(m));
199      }
200    }
201
202    Class sup = clazz.getSuperclass();
203    if(sup != null) {
204      popMeths(sup, meths);
205    }
206  }
207
208  private void popIMeths(Class clazz, Map meths) {
209    if(clazz.isInterface()) {
210      Method[] methods = clazz.getDeclaredMethods();
211      for(int i = 0; i < methods.length; i++) {
212        Method m = methods[i];
213        ArrayList sigList = new ArrayList();
214        sigList.add(m.getName());
215        sigList.addAll(Arrays.asList(m.getParameterTypes()));
216        if(!meths.containsKey(sigList)) {
217          meths.put(sigList, new IntrospectedCodeMethod(m));
218        }
219      }
220      Class[] interfaces = clazz.getInterfaces();
221      for(int i = 0; i < interfaces.length; i++) {
222        popIMeths(interfaces[i], meths);
223      }
224    }
225
226    Class sup = clazz.getSuperclass();
227    if(sup != null) {
228      popIMeths(sup, meths);
229    }
230  }
231
232  private List makeNameSig(CodeMethod m) {
233    List res = new ArrayList();
234    res.add(m.getName());
235
236    for(int i = 0; i < m.numParameters(); i++) {
237      res.add(m.getParameterType(i));
238    }
239
240    return res;
241  }
242
243  public CodeField getFieldByName(String name)
244          throws NoSuchFieldException {
245    try {
246      Field f = clazz.getField(name);
247      return new CodeField(this,
248                           name,
249                           IntrospectedCodeClass.forClass(f.getType()),
250                           f.getModifiers());
251    } catch (NoSuchFieldException ex) {
252      throw (NoSuchFieldException) new NoSuchFieldException(
253              "Can't find field " + name +
254              " in class " + getName()
255      ).initCause(ex);
256    }
257  }
258
259  private Set _fields;
260
261  public Set getFields() {
262    if(_fields == null) {
263      _fields = new HashSet();
264      Field[] fields = clazz.getFields();
265      for(int fi = 0; fi < fields.length; fi++) {
266        Field f = fields[fi];
267        _fields.add(new CodeField(this,
268                                  f.getName(),
269                                  IntrospectedCodeClass.forClass(f.getType()),
270                                  f.getModifiers()));
271      }
272
273      _fields = Collections.unmodifiableSet(_fields);
274    }
275
276    return _fields;
277  }
278
279  public Set getMethodsByName(String name) {
280    initMethods();
281
282    Set hits = (Set) _methsByName.get(name);
283    if(hits == null) {
284      return Collections.EMPTY_SET;
285    } else {
286      return hits;
287    }
288  }
289
290  public CodeMethod getMethod(String name, CodeClass[] args)
291          throws NoSuchMethodException
292  {
293    initMethods();
294
295    List nameSig = new ArrayList();
296    nameSig.add(name);
297    for(int i = 0; i < args.length; i++) {
298      nameSig.add(args[i]);
299    }
300
301    CodeMethod cm = (CodeMethod) _methsByNameSig.get(nameSig);
302
303    if(cm == null) {
304      throw new NoSuchMethodException(
305              "Could not find method " + getName() +
306              "." + name +
307              "(" + CodeUtils.classListToString(args) + ")");
308    }
309
310    return cm;
311  }
312
313
314  public CodeMethod getConstructor(CodeClass[] args)
315          throws NoSuchMethodException {
316    try {
317      Class[] argsC = new Class[args.length];
318      for (int i = 0; i < args.length; i++) {
319        argsC[i] = ((IntrospectedCodeClass) args[i]).clazz;
320      }
321      return new IntrospectedCodeConstructor(clazz.getConstructor(argsC));
322    } catch (NoSuchMethodException nsme) {
323      throw (NoSuchMethodException) new NoSuchMethodException(
324              "Could not find constructor new " + getName() +
325              "(" + CodeUtils.classListToString(args) + ")"
326      ).initCause(nsme);
327    }
328  }
329
330  public boolean isPrimitive() {
331    return clazz.isPrimitive();
332  }
333
334  public boolean isArray() {
335    return clazz.isArray();
336  }
337
338  public String toString() {
339    return this.getClass().getName() + ": " + clazz.getName();
340  }
341}