private static Method notBogus(Method method, String propertyName, Class<?> paramType) {
    if (method == null) return null;

    Container reader = Container.readerAnnotation(method);
    if (reader != null
        && (!reader.name().equals(propertyName)
            || !reader.enabled()
            || !method.getReturnType().isAssignableFrom(paramType))) {
      return null;
    } else {
      return method;
    }
  }
  private static MetaData buildMetaData(Class<? extends QObject> clazz) {
    MetaData metaData = new MetaData();

    List<Method> slots = new ArrayList<Method>();

    Hashtable<String, Method> propertyReaders = new Hashtable<String, Method>();
    Hashtable<String, Method> propertyWriters = new Hashtable<String, Method>();
    Hashtable<String, Object> propertyDesignables = new Hashtable<String, Object>();
    Hashtable<String, Method> propertyResetters = new Hashtable<String, Method>();
    Hashtable<String, Boolean> propertyUser = new Hashtable<String, Boolean>();

    // First we get all enums actually declared in the class
    Hashtable<String, Class<?>> enums = new Hashtable<String, Class<?>>();
    int enumConstantCount = queryEnums(clazz, enums);
    int enumCount = enums.size(); // Get the size before we add external enums

    Method declaredMethods[] = clazz.getDeclaredMethods();
    for (Method declaredMethod : declaredMethods) {

      if (!declaredMethod.isAnnotationPresent(QtBlockedSlot.class)
          && ((declaredMethod.getModifiers() & Modifier.STATIC) != Modifier.STATIC)) {

        // If we can't convert the type, we don't list it
        String methodParameters = methodParameters(declaredMethod);
        String returnType = declaredMethod.getReturnType().getName();
        if ((methodParameters.equals("")
                || !internalTypeNameOfParameters(declaredMethod, 1).equals(""))
            && (returnType.equals("")
                || returnType.equals("void")
                || !internalTypeNameOfClass(declaredMethod.getReturnType(), 0).equals(""))) {
          slots.add(declaredMethod);
        }
      }

      // Rules for readers:
      // 1. Zero arguments
      // 2. Return something other than void
      // 3. We can convert the type
      Container reader = Container.readerAnnotation(declaredMethod);
      {
        if (reader != null
            && reader.enabled()
            && isValidGetter(declaredMethod)
            && !internalTypeNameOfClass(declaredMethod.getReturnType(), 0).equals("")) {

          // If the return type of the property reader is not registered, then
          // we need to register the owner class in the meta object (in which case
          // it has to be a QObject)
          Class<?> returnType = declaredMethod.getReturnType();

          int count = putEnumTypeInHash(returnType, enums);
          if (count < 0) {
            System.err.println(
                "Problem in property '"
                    + reader.name()
                    + "' in '"
                    + clazz.getName()
                    + "': Only enum types 1. declared inside QObject subclasses or the "
                    + "Qt interface and 2. declared without the QtBlockedEnum annotation "
                    + "are supported for properties");
            continue;
          }

          propertyReaders.put(reader.name(), declaredMethod);
          propertyDesignables.put(reader.name(), isDesignable(declaredMethod, clazz));
          propertyUser.put(reader.name(), isUser(declaredMethod));
        }
      }

      // Rules for writers:
      // 1. Takes exactly one argument
      // 2. Return void
      // 3. We can convert the type
      Container writer = Container.writerAnnotation(declaredMethod);
      {
        if (writer != null && writer.enabled() && isValidSetter(declaredMethod)) {
          propertyWriters.put(writer.name(), declaredMethod);
        }
      }

      // Rules for resetters:
      // 1. No arguments
      // 2. Return void
      {
        Container resetter = Container.resetterAnnotation(declaredMethod);

        if (resetter != null
            && declaredMethod.getParameterTypes().length == 0
            && declaredMethod.getReturnType() == Void.TYPE) {
          propertyResetters.put(resetter.name(), declaredMethod);
        }
      }

      // Check naming convention by looking for setXxx patterns, but only if it hasn't already been
      // annotated as a writer
      String declaredMethodName = declaredMethod.getName();
      if (writer == null
          && reader
              == null // reader can't be a writer, cause the signature doesn't match, just an
                      // optimization
          && declaredMethodName.startsWith("set")
          && declaredMethodName.length() > 3
          && Character.isUpperCase(declaredMethodName.charAt(3))
          && isValidSetter(declaredMethod)) {

        Class<?> paramType = declaredMethod.getParameterTypes()[0];
        String propertyName =
            Character.toLowerCase(declaredMethodName.charAt(3)) + declaredMethodName.substring(4);

        if (!propertyReaders.containsKey(propertyName)) {
          // We need a reader as well, and the reader must not be annotated as disabled
          // The reader can be called 'xxx', 'getXxx', 'isXxx' or 'hasXxx'
          // (just booleans for the last two)
          Method readerMethod =
              notBogus(getMethod(clazz, propertyName, null), propertyName, paramType);
          if (readerMethod == null)
            readerMethod =
                notBogus(
                    getMethod(clazz, "get" + capitalizeFirst(propertyName), null),
                    propertyName,
                    paramType);
          if (readerMethod == null && isBoolean(paramType))
            readerMethod =
                notBogus(
                    getMethod(clazz, "is" + capitalizeFirst(propertyName), null),
                    propertyName,
                    paramType);
          if (readerMethod == null && isBoolean(paramType))
            readerMethod =
                notBogus(
                    getMethod(clazz, "has" + capitalizeFirst(propertyName), null),
                    propertyName,
                    paramType);

          if (readerMethod != null) { // yay
            reader = Container.readerAnnotation(readerMethod);
            if (reader == null) {
              propertyReaders.put(propertyName, readerMethod);
              propertyWriters.put(propertyName, declaredMethod);

              propertyDesignables.put(propertyName, isDesignable(readerMethod, clazz));
              propertyUser.put(propertyName, isUser(readerMethod));
            }
          }
        }
      }
    }

    Field declaredFields[] = clazz.getDeclaredFields();
    List<Field> signalFields = new ArrayList<Field>();
    List<QtJambiInternal.ResolvedSignal> resolvedSignals =
        new ArrayList<QtJambiInternal.ResolvedSignal>();
    for (Field declaredField : declaredFields) {
      if (QtJambiInternal.isSignal(declaredField.getType())) {
        // If we can't convert all the types we don't list the signal
        QtJambiInternal.ResolvedSignal resolvedSignal =
            QtJambiInternal.resolveSignal(declaredField, declaredField.getDeclaringClass());
        String signalParameters = signalParameters(resolvedSignal);
        if (signalParameters.length() == 0
            || internalTypeNameOfSignal(resolvedSignal.types, signalParameters, 1).length() != 0) {
          signalFields.add(declaredField);
          resolvedSignals.add(resolvedSignal);
        }
      }
    }
    metaData.signalsArray = signalFields.toArray(new Field[0]);

    {
      int functionCount = slots.size() + signalFields.size();
      int propertyCount = propertyReaders.keySet().size();
      int propertyNotifierCount = 0; // FIXME (see QTabWidget)
      int constructorCount = 0; // FIXME (see QObject)
      int metaObjectFlags = 0; // FIXME DynamicMetaObject

      // Until 4.7.x QtJambi used revision=1 however due to a change in the way
      //  4.7.x works some features of QtJambi stopped working.
      // revision 1         = MO_HEADER_LEN=10
      // revision 2 (4.5.x) = MO_HEADER_LEN=12 (added: constructorCount, constructorData)
      // revision 3         = MO_HEADER_LEN=13 (added: flags)
      // revision 4 (4.6.x) = MO_HEADER_LEN=14 (added: signalCount)
      // revision 5 (4.7.x) = MO_HEADER_LEN=14 (normalization)
      // revision 6 (4.8.x) = MO_HEADER_LEN=14 (added support for qt_static_metacall)
      // revision 7 (5.0.x) = MO_HEADER_LEN=14 (Qt5 to break backwards compatibility)
      // The format is compatible to share the same encoding code
      //  then we can change the revision to suit the Qt
      /// implementation we are working with.

      final int MO_HEADER_LEN = 14; // header size
      final int CLASSINFO_LEN = 2; // class info size
      int len = MO_HEADER_LEN + CLASSINFO_LEN;
      len += functionCount * 5;
      len += propertyCount * 3;
      len += propertyNotifierCount;
      len += enumCount * 4;
      len += enumConstantCount * 2;
      len += constructorCount * 5;
      metaData.metaData = new int[len + 1]; // add EOD <NUL>

      int intDataOffset = 0; // the offsets used by this descriptor are based on ints (32bit values)

      // Add static header
      metaData.metaData[0] = resolveMetaDataRevision(); // Revision
      // metaData[1] = 0 // class name  (ints default to offset 0 into strings)
      intDataOffset += MO_HEADER_LEN;

      metaData.metaData[2] = 1; // class info count
      metaData.metaData[3] = intDataOffset; // class info offset
      intDataOffset += CLASSINFO_LEN;

      // Functions always start right after this header at offset MO_HEADER_LEN + CLASSINFO_LEN
      metaData.metaData[4] = functionCount;
      metaData.metaData[5] = functionCount == 0 ? 0 : intDataOffset;
      intDataOffset += functionCount * 5; // Each function takes 5 ints

      metaData.metaData[6] = propertyCount;
      metaData.metaData[7] = propertyCount == 0 ? 0 : intDataOffset;
      intDataOffset +=
          (propertyCount * 3)
              + propertyNotifierCount; // Each property takes 3 ints and each propertNotifier 1 int

      // Enums
      metaData.metaData[8] = enumCount;
      metaData.metaData[9] = enumCount == 0 ? 0 : intDataOffset;
      intDataOffset +=
          (enumCount * 4)
              + (enumConstantCount * 2); // Each enum takes 4 ints and each enumConst takes 2 ints
      // revision 1 ends here

      metaData.metaData[10] = constructorCount;
      metaData.metaData[11] = constructorCount == 0 ? 0 : intDataOffset;
      intDataOffset += constructorCount * 5;
      // revision 2 ends here
      metaData.metaData[12] = metaObjectFlags; // flags
      // revision 3 ends here
      metaData.metaData[13] = signalFields.size(); // signalCount
      // revision 4, 5 and 6 ends here

      int offset = 0;
      int metaDataOffset = MO_HEADER_LEN; // Header is currently 14 ints long
      Hashtable<String, Integer> strings = new Hashtable<String, Integer>();
      List<String> stringsInOrder = new ArrayList<String>();
      // Class name
      {
        String className = clazz.getName().replace(".", "::");
        stringsInOrder.add(className);
        strings.put(className, offset);
        offset += className.length() + 1;
      }

      // Class info
      {
        offset +=
            addString(
                metaData.metaData,
                strings,
                stringsInOrder,
                "__qt__binding_shell_language",
                offset,
                metaDataOffset++);
        offset +=
            addString(
                metaData.metaData, strings, stringsInOrder, "Qt Jambi", offset, metaDataOffset++);
      }

      metaData.originalSignatures = new String[signalFields.size() + slots.size()];

      // Signals (### make sure enum types are covered)
      for (int i = 0; i < signalFields.size(); ++i) {
        Field signalField = signalFields.get(i);
        QtJambiInternal.ResolvedSignal resolvedSignal = resolvedSignals.get(i);

        String javaSignalParameters = signalParameters(resolvedSignal);
        metaData.originalSignatures[i] =
            resolvedSignal.name
                + (javaSignalParameters.length() > 0 ? '<' + javaSignalParameters + '>' : "");

        String signalParameters =
            internalTypeNameOfSignal(resolvedSignal.types, javaSignalParameters, 1);

        // Signal name
        offset +=
            addString(
                metaData.metaData,
                strings,
                stringsInOrder,
                resolvedSignal.name + "(" + signalParameters + ")",
                offset,
                metaDataOffset++);

        // Signal parameters
        offset +=
            addString(
                metaData.metaData,
                strings,
                stringsInOrder,
                signalParameters,
                offset,
                metaDataOffset++);

        // Signal type (signals are always void in Qt Jambi)
        offset +=
            addString(metaData.metaData, strings, stringsInOrder, "", offset, metaDataOffset++);

        // Signal tag (Currently not supported by the moc either)
        offset +=
            addString(metaData.metaData, strings, stringsInOrder, "", offset, metaDataOffset++);

        // Signal flags
        int flags = MethodSignal;
        int modifiers = signalField.getModifiers();
        if ((modifiers & Modifier.PRIVATE) == Modifier.PRIVATE) flags |= MethodAccessPrivate;
        else if ((modifiers & Modifier.PROTECTED) == Modifier.PROTECTED)
          flags |= MethodAccessProtected;
        else if ((modifiers & Modifier.PUBLIC) == Modifier.PUBLIC) flags |= MethodAccessPublic;
        metaData.metaData[metaDataOffset++] = flags;
      }

      // Slots (### make sure enum types are covered, ### also test QFlags)
      for (int i = 0; i < slots.size(); ++i) {
        Method slot = slots.get(i);

        String javaMethodSignature = methodSignature(slot);
        metaData.originalSignatures[signalFields.size() + i] = javaMethodSignature;

        // Slot signature
        offset +=
            addString(
                metaData.metaData,
                strings,
                stringsInOrder,
                internalTypeNameOfMethodSignature(slot, 1),
                offset,
                metaDataOffset++);

        // Slot parameters
        offset +=
            addString(
                metaData.metaData,
                strings,
                stringsInOrder,
                internalTypeNameOfParameters(slot, 1),
                offset,
                metaDataOffset++);

        // Slot type
        String returnType = slot.getReturnType().getName();
        if (returnType.equals("void")) returnType = "";
        offset +=
            addString(
                metaData.metaData,
                strings,
                stringsInOrder,
                internalTypeNameOfClass(slot.getReturnType(), 0),
                offset,
                metaDataOffset++);

        // Slot tag (Currently not supported by the moc either)
        offset +=
            addString(metaData.metaData, strings, stringsInOrder, "", offset, metaDataOffset++);

        // Slot flags
        int flags = MethodSlot;
        int modifiers = slot.getModifiers();
        if ((modifiers & Modifier.PRIVATE) == Modifier.PRIVATE) flags |= MethodAccessPrivate;
        else if ((modifiers & Modifier.PROTECTED) == Modifier.PROTECTED)
          flags |= MethodAccessProtected;
        else if ((modifiers & Modifier.PUBLIC) == Modifier.PUBLIC) flags |= MethodAccessPublic;

        metaData.metaData[metaDataOffset++] = flags;
      }
      metaData.slotsArray = slots.toArray(new Method[0]);

      String propertyNames[] = propertyReaders.keySet().toArray(new String[0]);
      metaData.propertyReadersArray = new Method[propertyNames.length];
      metaData.propertyResettersArray = new Method[propertyNames.length];
      metaData.propertyWritersArray = new Method[propertyNames.length];
      metaData.propertyDesignablesArray = new Method[propertyNames.length];
      for (int i = 0; i < propertyNames.length; ++i) {
        Method reader = propertyReaders.get(propertyNames[i]);
        Method writer = propertyWriters.get(propertyNames[i]);
        Method resetter = propertyResetters.get(propertyNames[i]);
        Object designableVariant = propertyDesignables.get(propertyNames[i]);
        boolean isUser = propertyUser.get(propertyNames[i]);

        if (writer != null
            && !reader.getReturnType().isAssignableFrom(writer.getParameterTypes()[0])) {
          System.err.println(
              "QtJambiInternal.buildMetaData: Writer for property "
                  + propertyNames[i]
                  + " takes a type which is incompatible with reader's return type.");
          writer = null;
        }

        // Name
        offset +=
            addString(
                metaData.metaData,
                strings,
                stringsInOrder,
                propertyNames[i],
                offset,
                metaDataOffset++);

        // Type (need to special case flags and enums)
        Class<?> t = reader.getReturnType();
        boolean isEnumOrFlags = Enum.class.isAssignableFrom(t) || QFlags.class.isAssignableFrom(t);

        String typeName = null;
        if (isEnumOrFlags
            && t.getDeclaringClass() != null
            && QObject.class.isAssignableFrom(t.getDeclaringClass())) {
          // To avoid using JObjectWrapper for enums and flags (which is required in certain cases.)
          typeName = t.getDeclaringClass().getName().replace(".", "::") + "::" + t.getSimpleName();
        } else {
          typeName = internalTypeNameOfClass(t, 0);
        }
        offset +=
            addString(
                metaData.metaData, strings, stringsInOrder, typeName, offset, metaDataOffset++);

        int designableFlags = 0;
        if (designableVariant instanceof Boolean) {
          if ((Boolean) designableVariant) designableFlags = PropertyDesignable;
        } else if (designableVariant instanceof Method) {
          designableFlags = PropertyResolveDesignable;
          metaData.propertyDesignablesArray[i] = (Method) designableVariant;
        }

        // Flags
        metaData.metaData[metaDataOffset++] =
            PropertyReadable
                | PropertyStored
                | designableFlags
                | (writer != null ? PropertyWritable : 0)
                | (resetter != null ? PropertyResettable : 0)
                | (isEnumOrFlags ? PropertyEnumOrFlag : 0)
                | (isUser ? PropertyUser : 0);

        metaData.propertyReadersArray[i] = reader;
        metaData.propertyWritersArray[i] = writer;
        metaData.propertyResettersArray[i] = resetter;
      }

      // Each property notifier takes 1 int // FIXME

      // Enum types
      int enumConstantOffset = metaDataOffset + enumCount * 4;

      Hashtable<String, Class<?>> enclosingClasses = new Hashtable<String, Class<?>>();
      Collection<Class<?>> classes = enums.values();
      for (Class<?> cls : classes) {
        Class<?> enclosingClass = cls.getEnclosingClass();
        if (enclosingClass.equals(clazz)) {
          // Name
          offset +=
              addString(
                  metaData.metaData,
                  strings,
                  stringsInOrder,
                  cls.getSimpleName(),
                  offset,
                  metaDataOffset++);

          // Flags (1 == flags, 0 == no flags)
          metaData.metaData[metaDataOffset++] = QFlags.class.isAssignableFrom(cls) ? 0x1 : 0x0;

          // Get the enum class
          Class<?> enumClass = Enum.class.isAssignableFrom(cls) ? cls : null;
          if (enumClass == null) {
            enumClass = getEnumForQFlags(cls);
          }

          enumConstantCount = enumClass.getEnumConstants().length;

          // Count
          metaData.metaData[metaDataOffset++] = enumConstantCount;

          // Data
          metaData.metaData[metaDataOffset++] = enumConstantOffset;

          enumConstantOffset += 2 * enumConstantCount;
        } else if (!enclosingClass.isAssignableFrom(clazz)
            && !enclosingClasses.contains(enclosingClass.getName())) {
          // If the enclosing class of an enum is not in the current class hierarchy, then
          // the generated meta object needs to have a pointer to its meta object in the
          // extra-data.
          enclosingClasses.put(enclosingClass.getName(), enclosingClass);
        }
      }
      metaData.extraDataArray = enclosingClasses.values().toArray(new Class<?>[0]);

      // Enum constants
      for (Class<?> cls : classes) {

        if (cls.getEnclosingClass().equals(clazz)) {
          // Get the enum class
          Class<?> enumClass = Enum.class.isAssignableFrom(cls) ? cls : null;
          if (enumClass == null) {
            enumClass = getEnumForQFlags(cls);
          }

          Enum<?> enumConstants[] = (Enum[]) enumClass.getEnumConstants();

          for (Enum<?> enumConstant : enumConstants) {
            // Key
            offset +=
                addString(
                    metaData.metaData,
                    strings,
                    stringsInOrder,
                    enumConstant.name(),
                    offset,
                    metaDataOffset++);

            // Value
            metaData.metaData[metaDataOffset++] =
                enumConstant instanceof QtEnumerator
                    ? ((QtEnumerator) enumConstant).value()
                    : enumConstant.ordinal();
          }
        }
      }

      // EOD
      metaData.metaData[metaDataOffset++] = 0;

      int stringDataOffset = 0;
      metaData.stringData = new byte[offset + 1];

      for (String s : stringsInOrder) {
        assert stringDataOffset == strings.get(s);
        System.arraycopy(s.getBytes(), 0, metaData.stringData, stringDataOffset, s.length());
        stringDataOffset += s.length();
        metaData.stringData[stringDataOffset++] = 0;
      }
    }

    return metaData;
  }