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     */
021    package org.biojava.utils.bytecode;
022    
023    import java.util.*;
024    import 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     */
041    public 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    }