whole_func_string: 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]))));
        }
      }
func_code_string: 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]))));
        }
      }
func_documentation_string: Parses all the source files recursively until all classes and all
  dependent classes are loaded
  
  @param cb