lib/qx/tool/compiler/Analyser.js
function(cb) {
      var t = this;
      if (!this.__db) {
        this.__db = {}; 
      }
      var db = this.__db;
      var metaWrittenLog = {};
      
      var compiledClasses = {};
      var metaFixupDescendants = {};
      var listenerId = this.addListener("compiledClass", function(evt) {
        var data = evt.getData();
        if (data.oldDbClassInfo) {
          if (data.oldDbClassInfo.extends) {
            metaFixupDescendants[data.oldDbClassInfo.extends] = true; 
          }
          if (data.oldDbClassInfo.implement) {
            data.oldDbClassInfo.implement.forEach(name => metaFixupDescendants[name] = true);
          }
          if (data.oldDbClassInfo.include) {
            data.oldDbClassInfo.include.forEach(name => metaFixupDescendants[name] = true);
          }
        }
        
        if (data.dbClassInfo.extends) {
          metaFixupDescendants[data.dbClassInfo.extends] = true; 
        }
        if (data.dbClassInfo.implement) {
          data.dbClassInfo.implement.forEach(name => metaFixupDescendants[name] = true);
        }
        if (data.dbClassInfo.include) {
          data.dbClassInfo.include.forEach(name => metaFixupDescendants[name] = true); 
        }

        compiledClasses[data.classFile.getClassName()] = data;
      });
      
      // Note that it is important to pre-load the classes in all libraries - this is because
      //  Babel plugins MUST be synchronous (ie cannot afford an async lookup of files on disk
      //  in mid parse)
      async.each(this.__libraries, function(library, cb) {
        library.scanForClasses(cb);
      }, function() {
        var classIndex = 0;
        var classes = t.__classes = t.__initialClassesToScan.toArray();

        function getConstructDependencies(className) {
          var deps = [];
          var info = t.__db.classInfo[className];
          if (info.dependsOn) {
            for (var depName in info.dependsOn) {
              if (info.dependsOn[depName].construct) {
                deps.push(depName);
              }
            }
          }
          return deps;
        }

        function getIndirectLoadDependencies(className) {
          var deps = [];
          var info = t.__db.classInfo[className];
          if (info && info.dependsOn) {
            for (var depName in info.dependsOn) {
              if (info.dependsOn[depName].load) {
                getConstructDependencies(depName).forEach(function(className) {
                  deps.push(className);
                });
              }
            }
          }
          return deps;
        }

        async.whilst(
            /* While */
            function() {
              return classIndex < classes.length;
            },
            /* Do */
            function(cb) {
              t.getClassInfo(classes[classIndex++], (err, dbClassInfo) => {
                if (dbClassInfo) {
                  var deps = dbClassInfo.dependsOn;
                  for (var depName in deps) {
                    t._addRequiredClass(depName);
                  }
                }
                if (err && err.code === "ENOCLASSFILE") {
                  console.error(err.message);
                  err = null;
                }
                return cb(err);
              });
            },
            /* Done */
            function(err) {
              if (err) {
                cb && cb(err);
                return;
              }
              classes.forEach(function(className) {
                var info = t.__db.classInfo[className];
                var deps = getIndirectLoadDependencies(className);
                deps.forEach(function(depName) {
                  if (!info.dependsOn) {
                    info.dependsOn = {}; 
                  }
                  if (!info.dependsOn[depName]) {
                    info.dependsOn[depName] = {}; 
                  }
                  info.dependsOn[depName].load = true;
                });
              });
              t.removeListenerById(listenerId);
              analyzeMeta()
                .then(() => cb())
                .catch(err => cb(err));
            }
        );
      });
      
      function fixupMetaData(classname, meta) {
        function fixupEntry(obj) {
          if (obj && obj.jsdoc) {
            qx.tool.compiler.jsdoc.Parser.parseJsDoc(obj.jsdoc, classname, t); 
          }
        }
        function fixupSection(sectionName) {
          var section = meta[sectionName];
          if (section) {
            for (var name in section) {
              fixupEntry(section[name]); 
            }
          }
        }

        fixupSection("properties");
        fixupSection("events");
        fixupSection("members");
        fixupSection("statics");
        fixupEntry(meta.clazz);
        fixupEntry(meta.construct);
        fixupEntry(meta.destruct);
        fixupEntry(meta.defer);
      }

      async function updateMetaData(classname, meta) {
        var classEntities = {
            members: {},
            properties: {}
        };
        
        async function analyseClassEntities(meta, first) {
          if (typeof meta == "string") {
            meta = await loadMetaData(meta); 
          }
          if (!meta) {
            return;
          }
          
          [ "members", "properties" ].forEach(entityTypeName => {
            if (!meta[entityTypeName]) {
              return;
            }
            
            for (let entityName in meta[entityTypeName]) {
              let entityMeta = meta[entityTypeName][entityName];
              if (entityMeta.type === "function" || entityTypeName === "properties") {
                var entityInfo = classEntities[entityTypeName][entityName];
                if (!entityInfo) {
                  entityInfo = classEntities[entityTypeName][entityName] = {
                    appearsIn: {},
                    overriddenFrom: null,
                    jsdoc: null,
                    abstract: meta.type === "interface",
                    mixin: meta.type === "mixin",
                    inherited: !first,
                    access: entityName.startsWith("__") ? "private" : entityName.startsWith("_") ? "protected" : "public"
                  };
                }
                if (entityMeta.property) {
                  entityInfo.property = entityMeta.property;
                } 
                if (meta.type === "mixin" && entityInfo.abstract) {
                  entityInfo.mixin = true; 
                }
                if (meta.type !== "interface") {
                  entityInfo.abstract = false;
                } else {
                  entityInfo["interface"] = true; 
                }

                if (!first) {
                  entityInfo.appearsIn[meta.className] = meta.type; 
                }
                if (!first && !entityInfo.overriddenFrom) {
                  entityInfo.overriddenFrom = meta.className; 
                }
                
                if (!entityInfo.jsdoc && hasSignature(entityMeta.jsdoc)) {
                  entityInfo.jsdoc = entityMeta.jsdoc;
                }
              }
            }
          });
          
          if (meta.interfaces) {
            for (let i = 0; i < meta.interfaces.length; i++) {
              await analyseClassEntities(meta.interfaces[i]); 
            }
          }
          if (meta.mixins) {
            for (let i = 0; i < meta.mixins.length; i++) {
              await analyseClassEntities(meta.mixins[i]);
            }
          }
          if (meta.superClass) {
            // Arrays of superclass are allowed for interfaces
            if (qx.lang.Type.isArray(meta.superClass)) {
              for (var i = 0; i < meta.superClass.length; i++) {
                await analyseClassEntities(meta.superClass[i]); 
              }
            } else {
              await analyseClassEntities(meta.superClass); 
            }
          }
          
          if (meta.properties) {
            function addPropertyAccessor(propertyMeta, methodName, accessorType, returnType, valueType, desc) {
              var entityInfo = classEntities.members[methodName];
              if (!entityInfo || entityInfo.abstract) {
                var newInfo = classEntities.members[methodName] = {
                    appearsIn: entityInfo ? entityInfo.appearsIn : {},
                    overriddenFrom: (entityInfo && entityInfo.appearsIn[0]) || null,
                    jsdoc: {
                      "@description": [
                        {
                          "name": "@description",
                          "body": desc
                        }
                      ]
                    },
                    property: accessorType,
                    inherited: !first,
                    mixin: propertyMeta.mixin,
                    access: "public"
                  };
                if (returnType) {
                  newInfo.jsdoc["@return"] = [
                      {
                        "name": "@return",
                        "type": returnType,
                        "desc": "Returns the value for " + propertyMeta.name
                      }
                    ];
                }
                if (valueType) {
                  newInfo.jsdoc["@param"] = [
                    {
                      "name": "@param",
                      "type": valueType,
                      "paramName": "value",
                      "desc": "Value for " + propertyMeta.name
                    }
                  ];
                }
              }
            }
            for (let propertyName in meta.properties) {
              let propertyMeta = meta.properties[propertyName];
              let upname = qx.lang.String.firstUp(propertyName);
              let type = propertyMeta.check || "any";
              
              let msg = "Gets the (computed) value of the property <code>" + propertyName + "</code>.\n" +
                "\n" +
                "For further details take a look at the property definition: {@link #" + propertyName + "}.";
              addPropertyAccessor(propertyMeta, "get" + upname, "get", type, null, msg);
              if (type == "Boolean") {
                addPropertyAccessor(propertyMeta, "is" + upname, "is", type, null, msg); 
              }
              
              addPropertyAccessor(propertyMeta, "set" + upname, "set", null, type, 
                  "Sets the user value of the property <code>" + propertyName + "</code>.\n" +
                  "\n" +
                  "For further details take a look at the property definition: {@link #" + propertyName + "}.");
              
              addPropertyAccessor(propertyMeta, "reset" + upname, "reset", null, null, 
                  "Resets the user value of the property <code>" + propertyName + "</code>.\n" +
                  "\n" + 
                  "The computed value falls back to the next available value e.g. appearance, init or inheritance value " +
                  "depending on the property configuration and value availability.\n" +
                  "\n" + 
                  "For further details take a look at the property definition: {@link #" + propertyName + "}.");
              
              if (propertyMeta.async) {
                msg = "Returns a {@link qx.Promise} which resolves to the (computed) value of the property <code>" + propertyName + "</code>." +
                "\n" +
                "For further details take a look at the property definition: {@link #" + propertyName + "}.";
                addPropertyAccessor(propertyMeta, "get" + upname + "Async", "getAsync", "Promise", null, msg);
                if (type == "Boolean") {
                  addPropertyAccessor(propertyMeta, "is" + upname + "Async", "isAsync", "Promise", null, msg); 
                }
                addPropertyAccessor(propertyMeta, "set" + upname + "Async", "setAsync", "Promise", type, 
                    "Sets the user value of the property <code>" + propertyName + "</code>, returns a {@link qx.Promise} " +
                    "which resolves when the value change has fully completed (in the case where there are asynchronous apply methods or events).\n" +
                    "\n" +
                    "For further details take a look at the property definition: {@link #" + propertyName + "}.");
              }
            }
          }
        }
        
        function hasSignature(jsdoc) {
          return jsdoc &&
            ((jsdoc["@param"] && jsdoc["@param"].length) || 
            (jsdoc["@return"] && jsdoc["@return"].length));
        }
        
        function mergeSignature(src, meta) {
          if (!src) {
            return; 
          }
          // src has nothing?  ignore it.  meta already has a signature?  preserve it
          if (!hasSignature(src) || hasSignature(meta.jsdoc)) {
            return; 
          }
          if (!meta.jsdoc) {
            meta.jsdoc = {}; 
          }
          if (src["@param"]) {
            meta.jsdoc["@param"] = qx.lang.Array.clone(src["@param"]);
          }
          if (src["@return"]) {
            meta.jsdoc["@return"] = qx.lang.Array.clone(src["@return"]);
          }
        }

        await analyseClassEntities(meta, true);
        
        if (meta.properties) {
          for (let propertyName in meta.properties) {
            let propertyMeta = meta.properties[propertyName];
            if (propertyMeta.refine) {
              let result = classEntities.properties[propertyName];
              if (result) {
                propertyMeta.overriddenFrom = result.overriddenFrom;
                propertyMeta.appearsIn = result.appearsIn;
                mergeSignature(result.jsdoc, propertyMeta);
              }
            }
          }
          
          for (let propertyName in classEntities.properties) {
            let propertyInfo = classEntities.properties[propertyName];
            if ((propertyInfo.abstract || propertyInfo.mixin) && !meta.properties[propertyInfo]) {
              let propertyMeta = meta.properties[propertyName] = {
                  type: "property",
                  name: propertyName,
                  abstract: Boolean(propertyInfo.abstract),
                  mixin: Boolean(propertyInfo.mixin),
                  access: propertyInfo.access,
                  overriddenFrom: propertyInfo.overriddenFrom
                };
              if (propertyInfo.appearsIn.length) {
                propertyMeta.appearsIn = Object.keys(propertyInfo.appearsIn); 
              }
              if (propertyMeta.appearsIn && !propertyMeta.appearsIn.length) {
                delete propertyMeta.appearsIn;
              }
              if (propertyInfo.jsdoc) {
                propertyMeta.jsdoc = propertyInfo.jsdoc; 
              }
              if (propertyInfo.overriddenFrom) {
                propertyMeta.overriddenFrom = propertyInfo.overriddenFrom;
              }
              if (!propertyMeta.overriddenFrom) {
                delete propertyMeta.overriddenFrom; 
              }
            }
          }
        }
        
        if (!meta.members) {
          meta.members = {};
        }
        for (let memberName in meta.members) {
          let memberMeta = meta.members[memberName];
          if (memberMeta.type === "function") {
            let result = classEntities.members[memberName];
            if (result) {
              memberMeta.overriddenFrom = result.overriddenFrom;
              memberMeta.appearsIn = Object.keys(result.appearsIn);
              mergeSignature(result.jsdoc, memberMeta);
            }
          }
        }
        for (let memberName in classEntities.members) {
          let memberInfo = classEntities.members[memberName];
          let memberMeta = meta.members[memberName];
          if (memberMeta && memberMeta.type === "variable" && memberInfo) {
            memberMeta.type = "function"; 
          }
          if ((memberInfo.abstract || memberInfo.mixin || memberInfo.property) && !memberMeta) {
            let memberMeta = meta.members[memberName] = {
                type: "function",
                name: memberName,
                abstract: Boolean(memberInfo.abstract),
                mixin: Boolean(memberInfo.mixin),
                inherited: Boolean(memberInfo.inherited),
                access: memberInfo.access,
                overriddenFrom: memberInfo.overriddenFrom
              };
            if (memberInfo.property) {
              memberMeta.property = memberInfo.property;
            }
            if (memberInfo.appearsIn.length) {
              memberMeta.appearsIn = Object.keys(memberInfo.appearsIn);
            }
            if (memberInfo.jsdoc) {
              memberMeta.jsdoc = memberInfo.jsdoc; 
            }
            if (memberInfo.overriddenFrom) {
              memberMeta.overriddenFrom = memberInfo.overriddenFrom; 
            }
            if (memberMeta.abstract) {
              meta.abstract = true;
            }
          }
        }
        for (let memberName in meta.members) {
          let memberMeta = meta.members[memberName];
          if (memberMeta.appearsIn && !memberMeta.appearsIn.length) {
            delete memberMeta.appearsIn;
          }
          if (!memberMeta.overriddenFrom) {
            delete memberMeta.overriddenFrom;
          }
        }
        if (Object.keys(meta.members).length == 0) {
          delete meta.members; 
        }
      }
      
      var cachedMeta = {};
      
      async function saveMetaData(classname, meta) {
        if (metaWrittenLog[classname]) {
          console.log(" *** ERRROR *** Writing " + classname + " more than once");
          throw new Error(" *** ERRROR *** Writing " + classname + " more than once");
        }
        metaWrittenLog[classname] = true;
        var filename = qx.tool.compiler.ClassFile.getOutputPath(t, classname) + "on";
        return writeFile(filename, JSON.stringify(meta, null, 2), {encoding: "utf-8"});
      }
      
      async function loadMetaData(classname) {
        if (classname == "Object" || classname == "Array" || classname == "Error") {
          return Promise.resolve(null); 
        }
        if (cachedMeta[classname]) {
          return Promise.resolve(cachedMeta[classname]); 
        }
        var filename = qx.tool.compiler.ClassFile.getOutputPath(t, classname) + "on";
        return readFile(filename, {encoding: "utf-8"})
          .then(str => jsonlint.parse(str))
          .then(meta => cachedMeta[classname] = meta)
          .catch(err => {
            console.error("Failed to load meta for " + classname + ": " + err);
          });
      }
      
      function calcDescendants(classname, meta) {
        meta.descendants = [];
        for (var name in db.classInfo) {
          var tmp = db.classInfo[name];
          if (tmp.extends == classname) {
            meta.descendants.push(name); 
          }
        }
      }
      
      async function analyzeMeta() {
        var toSave = {};
        for (let classname in compiledClasses) {
          let meta = cachedMeta[classname] = compiledClasses[classname].classFile.getOuterClassMeta();
          // Null meta means that the class didn't compile anything
          if (meta) {
            fixupMetaData(classname, meta); 
          }
        }
        
        for (let classname in compiledClasses) {
          let meta = cachedMeta[classname];
          if (meta) {
            await updateMetaData(classname, meta);
            calcDescendants(classname, meta);
            toSave[classname] = meta;
          }
        }
        
        var p = Promise.resolve();
        for (let classname in metaFixupDescendants) {
          if (!compiledClasses[classname] && db.classInfo[classname]) {
            p = p.then(() => loadMetaData(classname)
                .then(meta => {
                  if (meta) {
                    calcDescendants(classname, meta);
                    toSave[classname] = meta;
                  }
                }));
          }
        }

        await p.then(() => Promise.all(Object.keys(toSave).map(classname => saveMetaData(classname, toSave[classname]))));
      }
    }
function(cb) {
      var t = this;
      if (!this.__db) {
        this.__db = {}; 
      }
      var db = this.__db;
      var metaWrittenLog = {};
      
      var compiledClasses = {};
      var metaFixupDescendants = {};
      var listenerId = this.addListener("compiledClass", function(evt) {
        var data = evt.getData();
        if (data.oldDbClassInfo) {
          if (data.oldDbClassInfo.extends) {
            metaFixupDescendants[data.oldDbClassInfo.extends] = true; 
          }
          if (data.oldDbClassInfo.implement) {
            data.oldDbClassInfo.implement.forEach(name => metaFixupDescendants[name] = true);
          }
          if (data.oldDbClassInfo.include) {
            data.oldDbClassInfo.include.forEach(name => metaFixupDescendants[name] = true);
          }
        }
        
        if (data.dbClassInfo.extends) {
          metaFixupDescendants[data.dbClassInfo.extends] = true; 
        }
        if (data.dbClassInfo.implement) {
          data.dbClassInfo.implement.forEach(name => metaFixupDescendants[name] = true);
        }
        if (data.dbClassInfo.include) {
          data.dbClassInfo.include.forEach(name => metaFixupDescendants[name] = true); 
        }

        compiledClasses[data.classFile.getClassName()] = data;
      });
      
      // Note that it is important to pre-load the classes in all libraries - this is because
      //  Babel plugins MUST be synchronous (ie cannot afford an async lookup of files on disk
      //  in mid parse)
      async.each(this.__libraries, function(library, cb) {
        library.scanForClasses(cb);
      }, function() {
        var classIndex = 0;
        var classes = t.__classes = t.__initialClassesToScan.toArray();

        function getConstructDependencies(className) {
          var deps = [];
          var info = t.__db.classInfo[className];
          if (info.dependsOn) {
            for (var depName in info.dependsOn) {
              if (info.dependsOn[depName].construct) {
                deps.push(depName);
              }
            }
          }
          return deps;
        }

        function getIndirectLoadDependencies(className) {
          var deps = [];
          var info = t.__db.classInfo[className];
          if (info && info.dependsOn) {
            for (var depName in info.dependsOn) {
              if (info.dependsOn[depName].load) {
                getConstructDependencies(depName).forEach(function(className) {
                  deps.push(className);
                });
              }
            }
          }
          return deps;
        }

        async.whilst(
            /* While */
            function() {
              return classIndex < classes.length;
            },
            /* Do */
            function(cb) {
              t.getClassInfo(classes[classIndex++], (err, dbClassInfo) => {
                if (dbClassInfo) {
                  var deps = dbClassInfo.dependsOn;
                  for (var depName in deps) {
                    t._addRequiredClass(depName);
                  }
                }
                if (err && err.code === "ENOCLASSFILE") {
                  console.error(err.message);
                  err = null;
                }
                return cb(err);
              });
            },
            /* Done */
            function(err) {
              if (err) {
                cb && cb(err);
                return;
              }
              classes.forEach(function(className) {
                var info = t.__db.classInfo[className];
                var deps = getIndirectLoadDependencies(className);
                deps.forEach(function(depName) {
                  if (!info.dependsOn) {
                    info.dependsOn = {}; 
                  }
                  if (!info.dependsOn[depName]) {
                    info.dependsOn[depName] = {}; 
                  }
                  info.dependsOn[depName].load = true;
                });
              });
              t.removeListenerById(listenerId);
              analyzeMeta()
                .then(() => cb())
                .catch(err => cb(err));
            }
        );
      });
      
      function fixupMetaData(classname, meta) {
        function fixupEntry(obj) {
          if (obj && obj.jsdoc) {
            qx.tool.compiler.jsdoc.Parser.parseJsDoc(obj.jsdoc, classname, t); 
          }
        }
        function fixupSection(sectionName) {
          var section = meta[sectionName];
          if (section) {
            for (var name in section) {
              fixupEntry(section[name]); 
            }
          }
        }

        fixupSection("properties");
        fixupSection("events");
        fixupSection("members");
        fixupSection("statics");
        fixupEntry(meta.clazz);
        fixupEntry(meta.construct);
        fixupEntry(meta.destruct);
        fixupEntry(meta.defer);
      }

      async function updateMetaData(classname, meta) {
        var classEntities = {
            members: {},
            properties: {}
        };
        
        async function analyseClassEntities(meta, first) {
          if (typeof meta == "string") {
            meta = await loadMetaData(meta); 
          }
          if (!meta) {
            return;
          }
          
          [ "members", "properties" ].forEach(entityTypeName => {
            if (!meta[entityTypeName]) {
              return;
            }
            
            for (let entityName in meta[entityTypeName]) {
              let entityMeta = meta[entityTypeName][entityName];
              if (entityMeta.type === "function" || entityTypeName === "properties") {
                var entityInfo = classEntities[entityTypeName][entityName];
                if (!entityInfo) {
                  entityInfo = classEntities[entityTypeName][entityName] = {
                    appearsIn: {},
                    overriddenFrom: null,
                    jsdoc: null,
                    abstract: meta.type === "interface",
                    mixin: meta.type === "mixin",
                    inherited: !first,
                    access: entityName.startsWith("__") ? "private" : entityName.startsWith("_") ? "protected" : "public"
                  };
                }
                if (entityMeta.property) {
                  entityInfo.property = entityMeta.property;
                } 
                if (meta.type === "mixin" && entityInfo.abstract) {
                  entityInfo.mixin = true; 
                }
                if (meta.type !== "interface") {
                  entityInfo.abstract = false;
                } else {
                  entityInfo["interface"] = true; 
                }

                if (!first) {
                  entityInfo.appearsIn[meta.className] = meta.type; 
                }
                if (!first && !entityInfo.overriddenFrom) {
                  entityInfo.overriddenFrom = meta.className; 
                }
                
                if (!entityInfo.jsdoc && hasSignature(entityMeta.jsdoc)) {
                  entityInfo.jsdoc = entityMeta.jsdoc;
                }
              }
            }
          });
          
          if (meta.interfaces) {
            for (let i = 0; i < meta.interfaces.length; i++) {
              await analyseClassEntities(meta.interfaces[i]); 
            }
          }
          if (meta.mixins) {
            for (let i = 0; i < meta.mixins.length; i++) {
              await analyseClassEntities(meta.mixins[i]);
            }
          }
          if (meta.superClass) {
            // Arrays of superclass are allowed for interfaces
            if (qx.lang.Type.isArray(meta.superClass)) {
              for (var i = 0; i < meta.superClass.length; i++) {
                await analyseClassEntities(meta.superClass[i]); 
              }
            } else {
              await analyseClassEntities(meta.superClass); 
            }
          }
          
          if (meta.properties) {
            function addPropertyAccessor(propertyMeta, methodName, accessorType, returnType, valueType, desc) {
              var entityInfo = classEntities.members[methodName];
              if (!entityInfo || entityInfo.abstract) {
                var newInfo = classEntities.members[methodName] = {
                    appearsIn: entityInfo ? entityInfo.appearsIn : {},
                    overriddenFrom: (entityInfo && entityInfo.appearsIn[0]) || null,
                    jsdoc: {
                      "@description": [
                        {
                          "name": "@description",
                          "body": desc
                        }
                      ]
                    },
                    property: accessorType,
                    inherited: !first,
                    mixin: propertyMeta.mixin,
                    access: "public"
                  };
                if (returnType) {
                  newInfo.jsdoc["@return"] = [
                      {
                        "name": "@return",
                        "type": returnType,
                        "desc": "Returns the value for " + propertyMeta.name
                      }
                    ];
                }
                if (valueType) {
                  newInfo.jsdoc["@param"] = [
                    {
                      "name": "@param",
                      "type": valueType,
                      "paramName": "value",
                      "desc": "Value for " + propertyMeta.name
                    }
                  ];
                }
              }
            }
            for (let propertyName in meta.properties) {
              let propertyMeta = meta.properties[propertyName];
              let upname = qx.lang.String.firstUp(propertyName);
              let type = propertyMeta.check || "any";
              
              let msg = "Gets the (computed) value of the property <code>" + propertyName + "</code>.\n" +
                "\n" +
                "For further details take a look at the property definition: {@link #" + propertyName + "}.";
              addPropertyAccessor(propertyMeta, "get" + upname, "get", type, null, msg);
              if (type == "Boolean") {
                addPropertyAccessor(propertyMeta, "is" + upname, "is", type, null, msg); 
              }
              
              addPropertyAccessor(propertyMeta, "set" + upname, "set", null, type, 
                  "Sets the user value of the property <code>" + propertyName + "</code>.\n" +
                  "\n" +
                  "For further details take a look at the property definition: {@link #" + propertyName + "}.");
              
              addPropertyAccessor(propertyMeta, "reset" + upname, "reset", null, null, 
                  "Resets the user value of the property <code>" + propertyName + "</code>.\n" +
                  "\n" + 
                  "The computed value falls back to the next available value e.g. appearance, init or inheritance value " +
                  "depending on the property configuration and value availability.\n" +
                  "\n" + 
                  "For further details take a look at the property definition: {@link #" + propertyName + "}.");
              
              if (propertyMeta.async) {
                msg = "Returns a {@link qx.Promise} which resolves to the (computed) value of the property <code>" + propertyName + "</code>." +
                "\n" +
                "For further details take a look at the property definition: {@link #" + propertyName + "}.";
                addPropertyAccessor(propertyMeta, "get" + upname + "Async", "getAsync", "Promise", null, msg);
                if (type == "Boolean") {
                  addPropertyAccessor(propertyMeta, "is" + upname + "Async", "isAsync", "Promise", null, msg); 
                }
                addPropertyAccessor(propertyMeta, "set" + upname + "Async", "setAsync", "Promise", type, 
                    "Sets the user value of the property <code>" + propertyName + "</code>, returns a {@link qx.Promise} " +
                    "which resolves when the value change has fully completed (in the case where there are asynchronous apply methods or events).\n" +
                    "\n" +
                    "For further details take a look at the property definition: {@link #" + propertyName + "}.");
              }
            }
          }
        }
        
        function hasSignature(jsdoc) {
          return jsdoc &&
            ((jsdoc["@param"] && jsdoc["@param"].length) || 
            (jsdoc["@return"] && jsdoc["@return"].length));
        }
        
        function mergeSignature(src, meta) {
          if (!src) {
            return; 
          }
          // src has nothing?  ignore it.  meta already has a signature?  preserve it
          if (!hasSignature(src) || hasSignature(meta.jsdoc)) {
            return; 
          }
          if (!meta.jsdoc) {
            meta.jsdoc = {}; 
          }
          if (src["@param"]) {
            meta.jsdoc["@param"] = qx.lang.Array.clone(src["@param"]);
          }
          if (src["@return"]) {
            meta.jsdoc["@return"] = qx.lang.Array.clone(src["@return"]);
          }
        }

        await analyseClassEntities(meta, true);
        
        if (meta.properties) {
          for (let propertyName in meta.properties) {
            let propertyMeta = meta.properties[propertyName];
            if (propertyMeta.refine) {
              let result = classEntities.properties[propertyName];
              if (result) {
                propertyMeta.overriddenFrom = result.overriddenFrom;
                propertyMeta.appearsIn = result.appearsIn;
                mergeSignature(result.jsdoc, propertyMeta);
              }
            }
          }
          
          for (let propertyName in classEntities.properties) {
            let propertyInfo = classEntities.properties[propertyName];
            if ((propertyInfo.abstract || propertyInfo.mixin) && !meta.properties[propertyInfo]) {
              let propertyMeta = meta.properties[propertyName] = {
                  type: "property",
                  name: propertyName,
                  abstract: Boolean(propertyInfo.abstract),
                  mixin: Boolean(propertyInfo.mixin),
                  access: propertyInfo.access,
                  overriddenFrom: propertyInfo.overriddenFrom
                };
              if (propertyInfo.appearsIn.length) {
                propertyMeta.appearsIn = Object.keys(propertyInfo.appearsIn); 
              }
              if (propertyMeta.appearsIn && !propertyMeta.appearsIn.length) {
                delete propertyMeta.appearsIn;
              }
              if (propertyInfo.jsdoc) {
                propertyMeta.jsdoc = propertyInfo.jsdoc; 
              }
              if (propertyInfo.overriddenFrom) {
                propertyMeta.overriddenFrom = propertyInfo.overriddenFrom;
              }
              if (!propertyMeta.overriddenFrom) {
                delete propertyMeta.overriddenFrom; 
              }
            }
          }
        }
        
        if (!meta.members) {
          meta.members = {};
        }
        for (let memberName in meta.members) {
          let memberMeta = meta.members[memberName];
          if (memberMeta.type === "function") {
            let result = classEntities.members[memberName];
            if (result) {
              memberMeta.overriddenFrom = result.overriddenFrom;
              memberMeta.appearsIn = Object.keys(result.appearsIn);
              mergeSignature(result.jsdoc, memberMeta);
            }
          }
        }
        for (let memberName in classEntities.members) {
          let memberInfo = classEntities.members[memberName];
          let memberMeta = meta.members[memberName];
          if (memberMeta && memberMeta.type === "variable" && memberInfo) {
            memberMeta.type = "function"; 
          }
          if ((memberInfo.abstract || memberInfo.mixin || memberInfo.property) && !memberMeta) {
            let memberMeta = meta.members[memberName] = {
                type: "function",
                name: memberName,
                abstract: Boolean(memberInfo.abstract),
                mixin: Boolean(memberInfo.mixin),
                inherited: Boolean(memberInfo.inherited),
                access: memberInfo.access,
                overriddenFrom: memberInfo.overriddenFrom
              };
            if (memberInfo.property) {
              memberMeta.property = memberInfo.property;
            }
            if (memberInfo.appearsIn.length) {
              memberMeta.appearsIn = Object.keys(memberInfo.appearsIn);
            }
            if (memberInfo.jsdoc) {
              memberMeta.jsdoc = memberInfo.jsdoc; 
            }
            if (memberInfo.overriddenFrom) {
              memberMeta.overriddenFrom = memberInfo.overriddenFrom; 
            }
            if (memberMeta.abstract) {
              meta.abstract = true;
            }
          }
        }
        for (let memberName in meta.members) {
          let memberMeta = meta.members[memberName];
          if (memberMeta.appearsIn && !memberMeta.appearsIn.length) {
            delete memberMeta.appearsIn;
          }
          if (!memberMeta.overriddenFrom) {
            delete memberMeta.overriddenFrom;
          }
        }
        if (Object.keys(meta.members).length == 0) {
          delete meta.members; 
        }
      }
      
      var cachedMeta = {};
      
      async function saveMetaData(classname, meta) {
        if (metaWrittenLog[classname]) {
          console.log(" *** ERRROR *** Writing " + classname + " more than once");
          throw new Error(" *** ERRROR *** Writing " + classname + " more than once");
        }
        metaWrittenLog[classname] = true;
        var filename = qx.tool.compiler.ClassFile.getOutputPath(t, classname) + "on";
        return writeFile(filename, JSON.stringify(meta, null, 2), {encoding: "utf-8"});
      }
      
      async function loadMetaData(classname) {
        if (classname == "Object" || classname == "Array" || classname == "Error") {
          return Promise.resolve(null); 
        }
        if (cachedMeta[classname]) {
          return Promise.resolve(cachedMeta[classname]); 
        }
        var filename = qx.tool.compiler.ClassFile.getOutputPath(t, classname) + "on";
        return readFile(filename, {encoding: "utf-8"})
          .then(str => jsonlint.parse(str))
          .then(meta => cachedMeta[classname] = meta)
          .catch(err => {
            console.error("Failed to load meta for " + classname + ": " + err);
          });
      }
      
      function calcDescendants(classname, meta) {
        meta.descendants = [];
        for (var name in db.classInfo) {
          var tmp = db.classInfo[name];
          if (tmp.extends == classname) {
            meta.descendants.push(name); 
          }
        }
      }
      
      async function analyzeMeta() {
        var toSave = {};
        for (let classname in compiledClasses) {
          let meta = cachedMeta[classname] = compiledClasses[classname].classFile.getOuterClassMeta();
          // Null meta means that the class didn't compile anything
          if (meta) {
            fixupMetaData(classname, meta); 
          }
        }
        
        for (let classname in compiledClasses) {
          let meta = cachedMeta[classname];
          if (meta) {
            await updateMetaData(classname, meta);
            calcDescendants(classname, meta);
            toSave[classname] = meta;
          }
        }
        
        var p = Promise.resolve();
        for (let classname in metaFixupDescendants) {
          if (!compiledClasses[classname] && db.classInfo[classname]) {
            p = p.then(() => loadMetaData(classname)
                .then(meta => {
                  if (meta) {
                    calcDescendants(classname, meta);
                    toSave[classname] = meta;
                  }
                }));
          }
        }

        await p.then(() => Promise.all(Object.keys(toSave).map(classname => saveMetaData(classname, toSave[classname]))));
      }
    }
Parses all the source files recursively until all classes and all
dependent classes are loaded

@param cb