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.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     */
037    public 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    }