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]));
}