private void writeResolver(List introspectables, SourceWriter writer) {
    writer.println("public Class resolveClass(Object object){");
    writer.indent();

    for (Iterator it = introspectables.iterator(); it.hasNext(); ) {
      BeanResolver type = (BeanResolver) it.next();
      writer.println(
          "if( object instanceof "
              + type.getType().getQualifiedSourceName()
              + " ) return "
              + type.getType().getQualifiedSourceName()
              + ".class;");
    }

    writer.println("throw new RuntimeException( \"Object \"+object+\"could not be resolved.\" );");
    writer.outdent();
    writer.println("}");
  }
  private MethodWrapper[] findMethods(TreeLogger logger, List introspectables) {
    HashSet methods = new HashSet();

    for (Iterator it = introspectables.iterator(); it.hasNext(); ) {
      BeanResolver info = (BeanResolver) it.next();
      logger.branch(
          TreeLogger.DEBUG, "Method Scanning: " + info.getType().getQualifiedSourceName(), null);

      try {
        if (info.getProperties().size() == 0) {
          continue;
        }

        Collection<RProperty> pds = info.getProperties().values();

        for (RProperty p : pds) {
          if (p.getReadMethod() != null) {
            p.getReadMethod().hashWithType = true;
            methods.add(p.getReadMethod());
          }

          if (p.getWriteMethod() != null) {
            p.getWriteMethod().hashWithType = true;
            methods.add(p.getWriteMethod());
          }
        }
      } catch (Exception e) {
        logger.log(TreeLogger.ERROR, "Unable to introspect class. Is class a bean?", e);
      }
    }

    MethodWrapper[] results = new MethodWrapper[methods.size()];
    Iterator it = methods.iterator();

    for (int i = 0; it.hasNext(); i++) {
      results[i] = (MethodWrapper) it.next();
    }

    return results;
  }
  private void writeIntrospectables(
      TreeLogger logger, List introspectables, MethodWrapper[] methods, SourceWriter writer) {
    for (Iterator it = introspectables.iterator(); it.hasNext(); ) {
      BeanResolver bean = (BeanResolver) it.next();

      logger.branch(
          TreeLogger.DEBUG, "Introspecting: " + bean.getType().getQualifiedSourceName(), null);

      try {
        if (bean.getProperties().size() == 0) {
          continue;
        }

        writer.print("private static BeanDescriptor ");
        writer.print(bean.getType().getQualifiedSourceName().replaceAll("\\.", "_"));

        writer.println(" = null;");
      } catch (Exception e) {
        logger.log(TreeLogger.ERROR, "Unable to introspect class. Is class a bean?", e);
      }
    }
  }
  public String generate(TreeLogger logger, GeneratorContext context, String typeName)
      throws UnableToCompleteException {
    // .println("Introspector Generate.");
    try {
      this.objectType = context.getTypeOracle().getType("java.lang.Object");
    } catch (NotFoundException ex) {
      logger.log(TreeLogger.ERROR, typeName, ex);

      return null;
    }

    List<BeanResolver> introspectables =
        this.getIntrospectableTypes(logger, context.getTypeOracle());

    MethodWrapper[] methods = this.findMethods(logger, introspectables);

    ClassSourceFileComposerFactory mcf =
        new ClassSourceFileComposerFactory(this.packageName, this.methodsImplementationName);
    mcf.addImport(Method.class.getCanonicalName());

    PrintWriter methodsPrintWriter =
        context.tryCreate(logger, this.packageName, this.methodsImplementationName);

    if (methodsPrintWriter != null) {
      SourceWriter methodsWriter = mcf.createSourceWriter(context, methodsPrintWriter);
      this.writeMethods(logger, methods, methodsWriter);
      methodsWriter.println("}");
      context.commit(logger, methodsPrintWriter);
    }

    ClassSourceFileComposerFactory cfcf =
        new ClassSourceFileComposerFactory(this.packageName, this.implementationName);
    cfcf.addImplementedInterface(typeName);
    cfcf.addImport("java.util.HashMap");
    cfcf.addImport(Method.class.getCanonicalName());
    cfcf.addImport(com.totsp.gwittir.introspection.Property.class.getCanonicalName());
    cfcf.addImport(com.totsp.gwittir.introspection.BeanDescriptor.class.getCanonicalName());

    PrintWriter printWriter = context.tryCreate(logger, packageName, implementationName);

    if (printWriter == null) {
      // .println( "Introspector Generate skipped.");
      return packageName + "." + implementationName;
    }

    SourceWriter writer = cfcf.createSourceWriter(context, printWriter);
    this.writeIntrospectables(logger, introspectables, methods, writer);
    this.writeResolver(introspectables, writer);

    writer.println(
        "private HashMap<Class,BeanDescriptor> beanDescriptorLookup = new HashMap<Class,BeanDescriptor>();");
    writer.println();
    writer.println("public BeanDescriptor getDescriptor( Object object ){ ");
    writer.indent();
    writer.println(
        "if( object == null ) throw new NullPointerException(\"Attempt to introspect null object\");");
    writer.println(
        "if( object instanceof "
            + SelfDescribed.class.getCanonicalName()
            + " ) return ((SelfDescribed)object).__descriptor();");
    writer.println("BeanDescriptor descriptor = beanDescriptorLookup.get(object.getClass());");
    writer.println("if (descriptor!=null){");
    writer.indentln("return descriptor;");
    writer.outdent();
    writer.println("}");

    writer.println("descriptor=_getDescriptor(object);");
    writer.println("beanDescriptorLookup.put(object.getClass(),descriptor);");
    writer.println("return descriptor;");
    writer.outdent();
    writer.println("}");

    writer.println("private BeanDescriptor _getDescriptor( Object object ){ ");
    writer.indent();

    for (BeanResolver resolver : introspectables) {
      writer.println(
          "if( object instanceof " + resolver.getType().getQualifiedSourceName() + " ) {");
      writer.indent();

      String name = resolver.getType().getQualifiedSourceName().replaceAll("\\.", "_");
      logger.log(TreeLogger.DEBUG, "Writing : " + name, null);
      writer.print("return " + name + " == null ? " + name + " = ");
      this.writeBeanDescriptor(logger, resolver, methods, writer);
      writer.print(": " + name + ";");
      writer.outdent();
      writer.println("}");
    }

    writer.println(" throw new IllegalArgumentException(\"Unknown type\" + object.getClass() ); ");
    writer.outdent();
    writer.println("}");
    writer.println("public Object createInstance(Class clazz) {");
    writer.indent();
    for (BeanResolver resolver : introspectables) {
      boolean hasPNA = false;
      for (JConstructor constructor : resolver.getType().getConstructors()) {
        if (constructor.getParameters() == null
            || constructor.getParameters().length == 0 && constructor.isPublic()) {
          hasPNA = true;
          break;
        }
        if (hasPNA) {
          break;
        }
      }

      writer.println(
          "if(clazz.equals(" + resolver.getType().getQualifiedSourceName() + ".class)){");
      writer.indent();
      logger.log(
          TreeLogger.Type.TRACE,
          resolver.getType().getQualifiedSourceName()
              + " abstract "
              + resolver.getType().isAbstract()
              + " intf "
              + (resolver.getType().isInterface() != null)
              + " def "
              + resolver.getType().isDefaultInstantiable());
      if (resolver.getType().isAbstract() || resolver.getType().isInterface() != null) {
        writer.println("throw new IllegalArgumentException(clazz+\" is abstract\");");
      } else if (hasPNA) {
        writer.println("return new " + resolver.getType().getQualifiedSourceName() + "();");
      } else {
        writer.println(
            "throw new IllegalArgumentException(clazz+\" has no public no args constructor\");");
      }
      writer.outdent();
      writer.println("}");
    }
    writer.println(" throw new IllegalArgumentException(\"Unknown type\" +clazz ); ");
    writer.outdent();
    writer.println("}");

    writer.println("HashMap<String, Class> classes = new HashMap<String, Class>();");
    writer.println("public Class forName(String className){");
    writer.indent();
    writer.println("Class clazz = classes.get(className);");
    writer.println("if(clazz != null) return clazz;");
    for (BeanResolver resolver : introspectables) {
      writer.println(
          "if(className.equals(\"" + resolver.getType().getQualifiedSourceName() + "\")){");
      writer.indent();
      writer.println("clazz = " + resolver.getType().getQualifiedSourceName() + ".class;");
      writer.println("classes.put(className, clazz);");
      writer.println("return clazz;");
      writer.outdent();
      writer.println("}");
    }
    writer.println("throw new IllegalArgumentException(className+\" is not introspecable.\");");
    writer.outdent();
    writer.println("}");
    writer.outdent();
    writer.println("}");

    context.commit(logger, printWriter);

    // .println( "Introspector Generate completed.");
    return packageName + "." + implementationName;
  }
  private void writeBeanDescriptor(
      TreeLogger logger, BeanResolver info, MethodWrapper[] methods, SourceWriter writer) {
    writer.println("new BeanDescriptor() { ");
    writer.indent();
    writer.println("private HashMap lookup;");
    writer.println("private Property[] properties;");
    writer.println("public Property[] getProperties(){");
    writer.indent();

    {
      writer.println("if( this.properties != null ) ");
      writer.indentln("return this.properties;");
      writer.println("this.properties = new Property[" + (info.getProperties().size()) + "];");

      Collection pds = info.getProperties().values();
      String[] propertyNames = new String[pds.size()];
      logger.log(TreeLogger.SPAM, "" + (pds == null), null);

      if (pds != null) {
        int i = 0;

        for (Iterator it = pds.iterator(); it.hasNext(); i++) {
          RProperty p = (RProperty) it.next();
          propertyNames[i] = p.getName();
          writer.println("{");
          writer.indent();

          writer.print("Method readMethod = ");

          if (p.getReadMethod() == null) {
            writer.println("null;");
          } else {
            writer.println(
                this.packageName
                    + "."
                    + this.methodsImplementationName
                    + ".METHOD_"
                    + +this.find(methods, p.getReadMethod())
                    + ";");
          }

          writer.print("Method writeMethod = ");

          if (p.getWriteMethod() == null) {
            writer.println("null;");
          } else {
            writer.println(
                this.packageName
                    + "."
                    + this.methodsImplementationName
                    + ".METHOD_"
                    + +this.find(methods, p.getWriteMethod())
                    + ";");
          }

          logger.log(
              TreeLogger.DEBUG, p.getName() + " " + p.getType().getQualifiedSourceName(), null);

          JType ptype = this.resolveType(p.getType());

          logger.log(
              TreeLogger.DEBUG, p.getName() + " (Erased) " + ptype.getQualifiedSourceName(), null);
          writer.println(
              "this.properties["
                  + (i)
                  + "] = new Property( \""
                  + p.getName()
                  + "\", "
                  + ((p.getType() != null) ? ptype.getQualifiedSourceName() : "Object")
                  + ".class,  readMethod, writeMethod );");
          writer.outdent();
          writer.println("}");
        }
      }

      writer.println("return this.properties;");
    }

    writer.outdent();
    writer.println("} //end getProperties()");
    writer.println("public Property getProperty( String name ) {");
    writer.indent();
    // TODO Rewrite this to a nested if loop using the propertyNames parameter.
    writer.println("Property p = null;");
    writer.println("if( this.lookup != null ) {");
    writer.indentln("p = (Property) lookup.get(name); ");
    writer.println("} else {");
    writer.indent();
    writer.println("this.lookup = new HashMap();");
    writer.println("Property[] props = this.getProperties(); ");
    writer.println("for( int i=0; i < props.length; i++ ) {");
    writer.indent();
    writer.println("this.lookup.put( props[i].getName(), props[i] );");
    writer.outdent();
    writer.println("}");
    writer.println("p = (Property) this.lookup.get(name);");
    writer.outdent();
    writer.println("}");
    writer.println(
        "if( p == null ) throw new RuntimeException(\"Couldn't find property \"+name+\" for "
            + info.getType().getQualifiedSourceName()
            + "\");");
    writer.println("else return p;");
    writer.outdent();
    writer.println("}");

    writer.outdent();
    writer.print("}");
  }
  protected List<BeanResolver> getIntrospectableTypes(TreeLogger logger, TypeOracle oracle) {
    ArrayList<BeanResolver> results = new ArrayList<BeanResolver>();
    HashSet<BeanResolver> resolvers = new HashSet<BeanResolver>();
    HashSet<String> found = new HashSet<String>();

    try {
      JClassType[] types = oracle.getTypes();

      for (JClassType type : types) {
        if (!found.contains(type.getQualifiedSourceName())
            && isIntrospectable(logger, type)
            && (type.isInterface() == null)) {
          found.add(type.getQualifiedSourceName());
          resolvers.add(new BeanResolver(logger, type));
        }
      }

      // Do a crazy assed sort to make sure least
      // assignable types are at the bottom of the list
      results.addAll(resolvers);
      results.addAll(this.getFileDeclaredTypes(logger, oracle));

      boolean swap = true;

      // .print("Ordering "+results.size()+" by heirarchy ");
      while (swap) {
        // .print(".");
        swap = false;

        for (int i = results.size() - 1; i >= 0; i--) {
          BeanResolver type = (BeanResolver) results.get(i);

          for (int j = i - 1; j >= 0; j--) {
            BeanResolver check = (BeanResolver) results.get(j);

            if (type.getType().isAssignableTo(check.getType())) {
              results.set(i, check);
              results.set(j, type);

              type = check;

              swap = true;
            }
          }
        }
      }

      // System.out.println();
    } catch (Exception e) {
      logger.log(TreeLogger.ERROR, "Unable to finad Introspectable types.", e);
    }

    //        for(BeanResolver rs:results){
    //            logger.log(TreeLogger.ERROR, rs.toString());
    //        }
    // System.out.println("Found "+results.size()+" introspectable types.");
    for (BeanResolver resolver : results) {
      logger.log(
          TreeLogger.Type.INFO, "Introspectable: " + resolver.getType().getQualifiedSourceName());
    }
    logger.log(TreeLogger.Type.INFO, "Found introspectable types: " + results.size());
    return results;
  }