build-tools/scraper.js (242 lines of code) (raw):
var fs = require('fs');
var dependencies = [];
var alreadyGenerated = [];
var anonymousTypesToGenerate = [];
var anonymousTypeCount = 0;
var primitiveObjectTypes = {
    'string': 'String',
    'boolean': 'boolean',
    'integer': 'int',
    'number': 'double',
    'any': 'Object'
};
var primitiveOptionalObjectTypes = {
    'string': 'String',
    'boolean': 'Boolean',
    'integer': 'Integer',
    'number': 'Double',
    'any': 'Object'
};
function tab(amount) {
    amount = amount || 1;
    // two spaces per indent
    return new Array(amount + 1).join('  ');
}
function ret(amount) {
    amount = amount || 1;
    return new Array(amount + 1).join('\r\n');
}
function getAnonymousTypeName() {
    return 'AnonType' + anonymousTypeCount++;
}
function isPrimitive(typeName) {
    return primitiveObjectTypes.hasOwnProperty(typeName);
}
function isPrimitiveOrArray(typeName) {
    return isPrimitive(typeName) || typeName == 'array';
}
function resolveName(name) {
    var containsDot = name.indexOf('.') >= 0;
    var split = name.split('.');
    return {
        domain: containsDot ? split[0] : '',
        name: containsDot ? split[1] : name
    };
}
function findCommandOrEventDefinition(resolved) {
    var match = null;
    documentation.domains.forEach(function (domain) {
        if (!resolved.domain || resolved.domain == domain.domain) {
            if (domain.types) {
                var filterFunc = function(commandOrEvent) {
                    return commandOrEvent.name == resolved.name;
                };
                var matches = domain.commands.filter(filterFunc);
                matches = matches.concat(domain.events.filter(filterFunc));
                if (matches.length > 0) {
                    match = matches[0];
                    resolved.domain = domain.domain;
                }
            }
        }
    });
    return match;
}
function findTypeDefinition(resolved) {
    var match = null;
    documentation.domains.forEach(function (domain) {
        if (!resolved.domain || resolved.domain == domain.domain) {
            if (domain.types) {
                var matches = domain.types.filter(function (type) {
                    return type.id == resolved.name;
                });
                if (matches.length > 0) {
                    match = matches[0];
                    resolved.domain = domain.domain;
                }
            }
        }
    });
    return match;
}
function generateJavaTypeEquivalent(currentType, prop) {
    if (prop.hasOwnProperty('type')) {
        // if it's a primitive type, then just map to the java equivalent
        var type = primitiveObjectTypes.hasOwnProperty(prop.type) ? primitiveObjectTypes[prop.type] : prop.type;
        // if the property is optional, it is nullable
        if (prop.optional && primitiveOptionalObjectTypes.hasOwnProperty(prop.type)) {
            type = primitiveOptionalObjectTypes[prop.type];
        }
        if (prop.type == 'array') {
            if (prop.items.hasOwnProperty('$ref')) {
                if (currentType != prop.items.$ref) {
                    addDependencyIfNotGenerated(prop.items.$ref);
                }
                type = 'List<' + prop.items.$ref + '>';
            } else {
                type = getAnonymousTypeName();
                var typeDef = {
                    id: type,
                    type: prop.items.type,
                    properties: prop.items.properties
                };
                type = 'List<' + type + '>';
                anonymousTypesToGenerate.push(typeDef);
            }
        }
        return type;
    } else {
        var typeDefinition = findTypeDefinition(resolveName(prop.$ref));
        if (!isPrimitiveOrArray(typeDefinition.type)) {
            if (typeDefinition.type != currentType) {
                addDependencyIfNotGenerated(prop.$ref);
            }
            return prop.$ref;
        } else {
            var type = typeDefinition.type;
            var resolvedType = primitiveObjectTypes[type] || type;
            if (prop.optional) {
                if (primitiveOptionalObjectTypes.hasOwnProperty(type)) {
                    resolvedType = primitiveOptionalObjectTypes[type];
                }
            }
            if (type == 'array') {
                addDependencyIfNotGenerated(typeDefinition.items.$ref);
                return 'List<' + typeDefinition.items.$ref + '>';
            }
            return resolvedType;
        }
    }
}
function generateJavaClassForType(typeDefinition) {
    var result = 'public static class ' + typeDefinition.id + ' {';
    if (typeDefinition.properties != undefined) {
        typeDefinition.properties.forEach(function (prop) {
            result += ret() + tab() + '\@JsonProperty';
    
            if (!prop.optional) {
                result += '(required = true)';
            }
    
            result += ret();
    
            result += tab() + 'public ' + generateJavaTypeEquivalent(typeDefinition.id, prop) + ' ' + prop.name + ';' + ret();
        });
    } else {
        result += ret();
    }
    result += '}';
    return result;
}
function addDependencyIfNotGenerated(dependencyString) {
    var resolved = resolveName(dependencyString);
    if (!dependencyExists(resolved) && !isPrimitiveOrArray(findTypeDefinition(resolved).type)) {
        dependencies.push(resolved);
    }
}
function dependencyExists(resolvedDependency) {
    return dependencies.filter(function (existing) {
            return resolvedDependency.name == existing.name && resolvedDependency.domain == existing.domain;
        }).length != 0 ||
        alreadyGenerated.filter(function (existing) {
            return resolvedDependency.name == existing.name && resolvedDependency.domain == existing.domain;
        }).length != 0;
}
function generateCommandOrEvent(commandDef) {
    var className = commandDef.name.charAt(0).toUpperCase() + commandDef.name.slice(1);
    var hasParams = !!commandDef.parameters;
    var hasReturns = !!commandDef.returns;
    var paramsTypeName = className + 'Request';
    var returnsTypeName = className + 'Response';
    var result = '' +
        '@ChromeDevtoolsMethod' + ret() +
        'public JsonRpcResult ' + commandDef.name + '(JsonRpcPeer peer, JSONObject params) {' + ret();
    if (hasParams) {
        result += '' +
            tab(1) + 'final ' + paramsTypeName + ' = mObjectMapper.convertValue' + ret() +
            tab(2) + 'params,' + ret() +
            tab(2) + paramsTypeName + '.type);' + ret(2);
    }
    if (hasReturns) {
        result += '' +
            tab() + 'final ' + returnsTypeName + 'response = new ' + returnsTypeName + '();' + ret() +
            tab() + 'return response;' + ret();
    }
    result += '}' + ret(2);
    if (hasParams) {
        result += generateType({
                id: paramsTypeName,
                properties: commandDef.parameters
            }) + ret(2);
    }
    if (hasReturns) {
        result += generateType({
            id: returnsTypeName,
            properties: commandDef.returns
        });
    }
    return result;
}
function generateDependencies() {
    var result = '';
    while (dependencies.length > 0 || anonymousTypesToGenerate.length > 0) {
        while (dependencies.length > 0) {
            result += ret(2)
                + generateType(
                    findTypeDefinition(
                        dependencies.pop()));
        }
        while (anonymousTypesToGenerate.length > 0) {
            result += ret(2) +
                generateType(anonymousTypesToGenerate.pop());
        }
    }
    return result;
}
function generateType(typeDef) {
    alreadyGenerated.push(resolveName(typeDef.id));
    if (isPrimitiveOrArray(typeDef.type)) {
        return 'The type \'' + typeDef.id + '\' is a primitive type (' + typeDef.type + '), so no class generation is necessary.';
    }
    var result = generateJavaClassForType(typeDef);
    result += generateDependencies();
    return result;
}
function generate(name) {
    var resolved = resolveName(name);
    var command = findCommandOrEventDefinition(resolved);
    if (command != null) {
        return generateCommandOrEvent(command);
    }
    var type = findTypeDefinition(resolved);
    if (type != null) {
        return generateType(type);
    }
    return 'no command or type \'' + name + '\' found';
}
// first two args are path to node and to this file
var arguments = process.argv.slice(2);
if (arguments.length == 0) {
    console.log('usage:' + ret() +
        tab() + 'node scraper.js path_to_protocol_json name_of_method_or_type' + ret() +
        '  node scraper.js path_to_protocol_json domain.name_of_method_or_type' + ret() +
        'description:' + ret() +
        tab() + 'This script generates Java code representing a type or method defined in `protocol.json`,' +
        ' which can be found at: https://code.google.com/p/chromium/codesearch#chromium/src/third_party/WebKit/Source/devtools/protocol.json')
} else {
    var documentation = JSON.parse(fs.readFileSync(arguments[0], {encoding: 'utf8'}));
    console.log(generate(arguments[1]));
}