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.io.*;
025
026/**
027 * A CodeClass implementation that is used to generate new classes.
028 *
029 * <p>
030 * When creating classes, instantiate one of these, add fields and methods.
031 * Associate CodeGenerator instances with methods. Then, use
032 * GeneratedClassLoader to make a new class.
033 * </p>
034 *
035 * @author Matthew Pocock
036 */
037public class GeneratedCodeClass implements CodeClass {
038  private String name;
039  private CodeClass superClass;
040  private List interfaces;
041  private int modifiers;
042  private Map methods;
043  private Map fields;
044  private String sourceFile;
045  private boolean deprecated;
046
047  {
048    methods = new HashMap();
049    fields = new HashMap();
050    sourceFile = null;
051  }
052
053  public GeneratedCodeClass(
054          String name,
055          Class superClass,
056          Class[] interfaces,
057          int modifiers
058          ) throws CodeException
059  {
060    this.name = name;
061    this.modifiers = modifiers;
062    this.superClass = IntrospectedCodeClass.forClass(superClass);
063    this.interfaces = new ArrayList(Arrays.asList(interfaces));
064    for (Iterator i = this.interfaces.iterator(); i.hasNext();) {
065      Class clazz = (Class) i.next();
066      if (!clazz.isInterface()) {
067        throw new CodeException(
068                "Attempted to create class implemneting non-interface " + clazz
069        );
070      }
071    }
072  }
073
074  public GeneratedCodeClass(String name,
075                            CodeClass superClass,
076                            CodeClass[] interfaces,
077                            int modifiers)
078          throws CodeException
079  {
080    this.name = name;
081    this.modifiers = modifiers;
082    this.superClass = superClass;
083    this.interfaces = new ArrayList(Arrays.asList(interfaces));
084    for (Iterator i = this.interfaces.iterator(); i.hasNext();) {
085      Object obj = i.next();
086      if (!(obj instanceof CodeClass)) {
087        throw new CodeException(
088                "Interface list must contain CodeClass instances"
089        );
090      }
091    }
092  }
093
094  /**
095   * Set the source file associated with this code class.
096   *
097   * <p>
098   * The source file appears in debugging output and stack traces. Use this
099   * method to set the source file that this generated class will clame to be
100   * from. You can use non-file names e.g. uri:myGenerator:proxy/foo
101   * </p>
102   *
103   * <p>
104   * To un-set the source file, use null.
105   * </p>
106   *
107   * @param sourceFile  the source file for this class
108   */
109  public void setSourceFile(String sourceFile) {
110    this.sourceFile = sourceFile;
111  }
112
113  /**
114   * Get the source file associated with this code class.
115   *
116   * <p>
117   * Null indicates that no source file is set.
118   * </p>
119   *
120   * @return the source file for this code class
121   */
122  public String getSourceFile() {
123    return sourceFile;
124  }
125
126  /**
127   * Set the deprecation flag.
128   *
129   * <p>
130   * If deprecated is true, the class will be flagged as deprecated.
131   * </p>
132   *
133   * @param deprecated  the new value of the deprecation
134   */
135  public void setDeprecated(boolean deprecated) {
136    this.deprecated = deprecated;
137  }
138
139  /**
140   * Get the deprecation flag.
141   *
142   * @return  wether or not this class is deprecated
143   */
144  public boolean isDeprecated() {
145    return deprecated;
146  }
147
148  public List getInterfaces() {
149    return Collections.unmodifiableList(interfaces);
150  }
151
152  public Set getMethods() {
153    return methods.keySet();
154  }
155
156  public Set getMethodsByName(String name) {
157    Set all = getMethods();
158    Set some = new HashSet();
159    for (Iterator i = all.iterator(); i.hasNext();) {
160      CodeMethod m = (CodeMethod) i.next();
161      if (m.getName().equals(name)) {
162        some.add(m);
163      }
164    }
165    return some;
166  }
167
168  public CodeMethod getConstructor(CodeClass[] args)
169          throws NoSuchMethodException
170  {
171    return getMethod("<init>", args);
172  }
173
174  public CodeMethod getMethod(String name, CodeClass[] args)
175          throws NoSuchMethodException
176  {
177    Set poss = getMethodsByName(name);
178    METHOD_LOOP:
179     for (Iterator i = poss.iterator(); i.hasNext();) {
180       CodeMethod meth = (CodeMethod) i.next();
181       if (meth.numParameters() != args.length) {
182         continue METHOD_LOOP;
183       }
184       for (int j = 0; j < args.length; j++) {
185         if (!meth.getParameterType(j).equals(args[j])) {
186           continue METHOD_LOOP;
187         }
188       }
189       return meth;
190     }
191
192    StringBuffer methodSig = new StringBuffer(
193            "Could not find method " + getName() + "." + name + "("
194    );
195    if (args.length > 0) {
196      methodSig.append(args[0].getName());
197    }
198    for (int i = 1; i < args.length; i++) {
199      methodSig.append(",");
200      methodSig.append(args[i].getName());
201    }
202    methodSig.append(")");
203    throw new NoSuchMethodException(methodSig.toString());
204  }
205
206  public Set getFields() {
207    return fields.keySet();
208  }
209
210  public CodeClass getSuperClass() {
211    return superClass;
212  }
213
214  public CodeField getFieldByName(String name)
215          throws NoSuchFieldException
216  {
217    CodeField f = (CodeField) fields.get(name);
218    if (f == null) {
219      throw new NoSuchFieldException("No field for " + name + " in class " + getName());
220    }
221    return f;
222  }
223
224  public String getName() {
225    return name;
226  }
227
228  public String getJName() {
229    String name = getName();
230    StringBuffer sb = new StringBuffer();
231    for (int i = 0; i < name.length(); ++i) {
232      char c = name.charAt(i);
233      if (c == '.')
234        sb.append('/');
235      else
236        sb.append(c);
237    }
238    return sb.toString();
239  }
240
241  public int getModifiers() {
242    return modifiers;
243  }
244
245  public String getDescriptor() {
246    String name = getName();
247    StringBuffer sb = new StringBuffer();
248    sb.append('L');
249    for (int i = 0; i < name.length(); ++i) {
250      char c = name.charAt(i);
251      if (c == '.')
252        sb.append('/');
253      else
254        sb.append(c);
255    }
256    sb.append(';');
257    return sb.toString();
258  }
259
260  /**
261   * Create a new method.
262   *
263   * <p>
264   * This defines the shape of a method that will be generated. Use
265   * {@link #setCodeGenerator} to associate code with the method.
266   * </p>
267   *
268   * <p>
269   * The argNames will become the names of local variables for each argument.
270   * </p>
271   *
272   * @param name      the method name
273   * @param type      the return type
274   * @param args      arguments taken
275   * @param argNames  names of the arguments
276   * @param mods      access modifiers
277   * @return          a new GeneratedCodeMethod
278   * @throws CodeException if the method could not be created
279   */
280  public GeneratedCodeMethod createMethod(
281          String name,
282          CodeClass type,
283          CodeClass[] args,
284          String[] argNames,
285          int mods
286          )
287          throws CodeException
288  {
289    GeneratedCodeMethod cm = new GeneratedCodeMethod(this, name, type, args, argNames, mods);
290    if (methods.containsKey(cm)) {
291      throw new CodeException("Attempt to create multiple methods with same signatures.");
292    }
293
294    methods.put(cm, null);
295    return cm;
296  }
297
298  /**
299   * Create a new method.
300   *
301   * <p>
302   * This defines the shape of a method that will be generated. Use
303   * {@link #setCodeGenerator} to associate code with the method.
304   * </p>
305   *
306   * @param name      the method name
307   * @param type      the return type
308   * @param args      arguments taken
309   * @param mods      access modifiers
310   * @return          a new GeneratedCodeMethod
311   * @throws CodeException if the method could not be created
312   */
313  public GeneratedCodeMethod createMethod(
314          String name,
315          CodeClass type,
316          CodeClass[] args,
317          int mods
318          )
319          throws CodeException
320  {
321    return createMethod(name, type, args, new String[0], mods);
322  }
323
324  public CodeField createField(String name, CodeClass clazz, int mods)
325          throws CodeException
326  {
327    if (fields.containsKey(name)) {
328      throw new CodeException("Attempt to create multiple fields named " + name);
329    }
330
331    CodeField cf = new CodeField(this, name, clazz, mods);
332    fields.put(name, cf);
333    return cf;
334  }
335
336  public void setCodeGenerator(CodeMethod method, CodeGenerator cg)
337          throws CodeException
338  {
339    if (!methods.containsKey(method)) {
340      throw new CodeException("Class doesn't provide method " + method.getName());
341    }
342
343    methods.put(method, cg);
344  }
345
346  public void createCode(OutputStream os)
347          throws IOException, CodeException
348  {
349    DataOutputStream dos = new DataOutputStream(os);
350
351    // Write classfile header
352
353    dos.writeInt((int) (0xcafebabe));    // Magic
354    dos.writeShort(3);                  // Minor  version
355    dos.writeShort(45);                   // Major version (check!)
356
357    ConstantPool cp = new ConstantPool();
358
359    // The rest of the classfile gets written to a buffer, accumulating a constant pool along the way
360
361    ByteArrayOutputStream baos = new ByteArrayOutputStream();
362    DataOutputStream bdos = new DataOutputStream(baos);
363
364    bdos.writeShort(modifiers);
365    bdos.writeShort(cp.resolveClass(this));         // this-class ID
366    bdos.writeShort(cp.resolveClass(superClass));   // super-class ID
367    bdos.writeShort(interfaces.size());             // number_of_interfaces
368    for (Iterator i = interfaces.iterator(); i.hasNext();) {
369      bdos.writeShort(cp.resolveClass((CodeClass) i.next())); // interface ID
370    }
371
372    // Write the fields
373
374    bdos.writeShort(fields.size());
375    for (Iterator i = fields.values().iterator(); i.hasNext();) {
376      CodeField cf = (CodeField) i.next();
377      bdos.writeShort(cf.getModifiers());
378      bdos.writeShort(cp.resolveUtf8(cf.getName()));
379      bdos.writeShort(cp.resolveUtf8(cf.getType().getDescriptor()));
380      bdos.writeShort(0); // No attributes right now
381    }
382
383    // Write the methods (wahey!)
384
385    Set methSet = methods.entrySet();
386    bdos.writeShort(methSet.size());
387    for (Iterator i = methSet.iterator(); i.hasNext();) {
388      Map.Entry me = (Map.Entry) i.next();
389      GeneratedCodeMethod cm = (GeneratedCodeMethod) me.getKey();
390      CodeGenerator cg = (CodeGenerator) me.getValue();
391
392      bdos.writeShort(cm.getModifiers());                   // access_flags
393      bdos.writeShort(cp.resolveUtf8(cm.getName()));        // name_index
394      bdos.writeShort(cp.resolveUtf8(cm.getDescriptor()));  // descriptor_index
395
396      // Actually generate the code
397      MethodRootContext ctx = new MethodRootContext(this, cm, cp);
398      ctx.open();
399
400      LocalVariable thisP = cm.getThis();
401      if (thisP != null) {
402        // Non-static method
403        ctx.resolveLocal(thisP);
404      }
405      for (int parm = 0; parm < cm.numParameters(); ++parm) {
406        ctx.resolveLocal(cm.getVariable(parm));
407      }
408
409      cg.writeCode(ctx);
410      ctx.close();
411
412      Set thrownExceptions = cm.getThrownExceptions();
413
414      // number of method attirbutes
415      int numMethAttrs = 1; // we always have code
416
417      // do we have exceptions?
418      if(!thrownExceptions.isEmpty()) {
419        numMethAttrs++;
420      }
421
422      bdos.writeShort(numMethAttrs);                        // attributes_count
423
424      // start attribute_info for method
425
426      // Code attribute
427      List exceptionTable = ctx.getExceptionTable();
428
429      bdos.writeShort(cp.resolveUtf8("Code"));
430      bdos.writeInt(12 + ctx.getOffset() + exceptionTable.size() * 8);
431      bdos.writeShort(cg.stackDepth());
432      bdos.writeShort(ctx.getMaxLocals());
433      bdos.writeInt(ctx.getOffset());
434      ctx.writeTo(bdos);
435      bdos.writeShort(exceptionTable.size());
436      for (Iterator ei = exceptionTable.iterator(); ei.hasNext();) {
437        ExceptionMemento em = (ExceptionMemento) ei.next();
438        if (!(em.isFullyResolved()))
439          throw new CodeException("Exception table entry refers to unresolved label");
440        bdos.writeShort(em.startHandled.getOffset());
441        bdos.writeShort(em.endHandled.getOffset());
442        bdos.writeShort(em.handler.getOffset());
443        if (em.eClass != null)
444          bdos.writeShort(cp.resolveClass(em.eClass));
445        else
446          bdos.writeShort(0); // For `finally'
447      }
448      bdos.writeShort(0); // Code has no sub-attributes
449
450      // Exceptions attribute
451      if (thrownExceptions.size() > 0) {
452        bdos.writeShort(cp.resolveUtf8("Exceptions"));  // attribute_name_index
453        bdos.writeInt(2 + thrownExceptions.size() * 2); // attribute_length
454        bdos.writeShort(thrownExceptions.size());       // number_of_exceptions
455        for (Iterator tei = thrownExceptions.iterator(); tei.hasNext();) {
456          CodeClass exClass = (CodeClass) tei.next();
457          bdos.writeShort(cp.resolveClass(exClass));    // exception class
458        }
459      }
460    }
461
462    // class-wide attributes
463    //
464    // currently, these are SourceFile and Deprecated only
465    int classAttributes = 0;
466
467    if(sourceFile != null) {
468      classAttributes++;
469    }
470    if(deprecated) {
471      classAttributes++;
472    }
473
474    bdos.writeShort(classAttributes);  // attributes_count
475
476    // write the source file attribute
477    if(sourceFile != null) {
478      bdos.writeShort(cp.resolveUtf8("SourceFile"));  // attribute_name_index
479      bdos.writeInt(2);                               // attribute_length
480      bdos.writeShort(cp.resolveUtf8(sourceFile));    // sourcefile_index
481    }
482
483    // write the deprecate attribute
484    if(isDeprecated()) {
485      bdos.writeShort(cp.resolveUtf8("Deprecated"));  // attribute_name_index
486      bdos.writeInt(0);                               // attribute_length
487    }
488
489    // All constants will now have been resolved, so we can finally write the cpool
490
491    dos.writeShort(cp.constantPoolSize());
492    cp.writeConstantPool(dos);
493
494    // Append the rest of the classfile to the stream
495
496    baos.writeTo(dos);
497  }
498
499  public boolean isPrimitive() {
500    return false;
501  }
502
503  public boolean isArray() {
504    return false;
505  }
506}