/*
* Copyright (c) 2014 MKLab. All rights reserved.
*
* Permission is hereby granted, free of charge, to any person obtaining a
* copy of this software and associated documentation files (the "Software"),
* to deal in the Software without restriction, including without limitation
* the rights to use, copy, modify, merge, publish, distribute, sublicense,
* and/or sell copies of the Software, and to permit persons to whom the
* Software is furnished to do so, subject to the following conditions:
*
* The above copyright notice and this permission notice shall be included in
* all copies or substantial portions of the Software.
*
* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
* IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
* FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
* AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
* LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING
* FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER
* DEALINGS IN THE SOFTWARE.
*
*/
/*jslint vars: true, plusplus: true, devel: true, nomen: true, indent: 4, maxerr: 50, regexp: true */
/*global define, $, _, window, app, type, document, java7 */
define(function (require, exports, module) {
"use strict";
var Repository = app.getModule("core/Repository"),
ProjectManager = app.getModule("engine/ProjectManager"),
Engine = app.getModule("engine/Engine"),
FileSystem = app.getModule("filesystem/FileSystem"),
FileUtils = app.getModule("file/FileUtils"),
Async = app.getModule("utils/Async"),
UML = app.getModule("uml/UML");
var CodeGenUtils = require("CodeGenUtils");
/**
* Java Code Generator
* @constructor
*
* @param {type.UMLPackage} baseModel
* @param {string} basePath generated files and directories to be placed
*/
function JavaCodeGenerator(baseModel, basePath) {
/** @member {type.Model} */
this.baseModel = baseModel;
/** @member {string} */
this.basePath = basePath;
}
/**
* Return Indent String based on options
* @param {Object} options
* @return {string}
*/
JavaCodeGenerator.prototype.getIndentString = function (options) {
if (options.useTab) {
return "\t";
} else {
var i, len, indent = [];
for (i = 0, len = options.indentSpaces; i < len; i++) {
indent.push(" ");
}
return indent.join("");
}
};
/**
* Generate codes from a given element
* @param {type.Model} elem
* @param {string} path
* @param {Object} options
* @return {$.Promise}
*/
JavaCodeGenerator.prototype.generate = function (elem, path, options) {
var result = new $.Deferred(),
self = this,
fullPath,
directory,
codeWriter,
file;
// Package
if (elem instanceof type.UMLPackage) {
fullPath = path + "/" + elem.name;
directory = FileSystem.getDirectoryForPath(fullPath);
directory.create(function (err, stat) {
if (!err) {
Async.doSequentially(
elem.ownedElements,
function (child) {
return self.generate(child, fullPath, options);
},
false
).then(result.resolve, result.reject);
} else {
result.reject(err);
}
});
} else if (elem instanceof type.UMLClass) {
// AnnotationType
if (elem.stereotype === "annotationType") {
fullPath = path + "/" + elem.name + ".java";
codeWriter = new CodeGenUtils.CodeWriter(this.getIndentString(options));
this.writePackageDeclaration(codeWriter, elem, options);
codeWriter.writeLine();
codeWriter.writeLine("import java.util.*;");
codeWriter.writeLine();
this.writeAnnotationType(codeWriter, elem, options);
file = FileSystem.getFileForPath(fullPath);
FileUtils.writeText(file, codeWriter.getData(), true).then(result.resolve, result.reject);
// Class
} else {
fullPath = path + "/" + elem.name + ".java";
codeWriter = new CodeGenUtils.CodeWriter(this.getIndentString(options));
this.writePackageDeclaration(codeWriter, elem, options);
codeWriter.writeLine();
codeWriter.writeLine("import java.util.*;");
codeWriter.writeLine();
this.writeClass(codeWriter, elem, options);
file = FileSystem.getFileForPath(fullPath);
FileUtils.writeText(file, codeWriter.getData(), true).then(result.resolve, result.reject);
}
// Interface
} else if (elem instanceof type.UMLInterface) {
fullPath = path + "/" + elem.name + ".java";
codeWriter = new CodeGenUtils.CodeWriter(this.getIndentString(options));
this.writePackageDeclaration(codeWriter, elem, options);
codeWriter.writeLine();
codeWriter.writeLine("import java.util.*;");
codeWriter.writeLine();
this.writeInterface(codeWriter, elem, options);
file = FileSystem.getFileForPath(fullPath);
FileUtils.writeText(file, codeWriter.getData(), true).then(result.resolve, result.reject);
// Enum
} else if (elem instanceof type.UMLEnumeration) {
fullPath = path + "/" + elem.name + ".java";
codeWriter = new CodeGenUtils.CodeWriter(this.getIndentString(options));
this.writePackageDeclaration(codeWriter, elem, options);
codeWriter.writeLine();
this.writeEnum(codeWriter, elem, options);
file = FileSystem.getFileForPath(fullPath);
FileUtils.writeText(file, codeWriter.getData(), true).then(result.resolve, result.reject);
// Others (Nothing generated.)
} else {
result.resolve();
}
return result.promise();
};
/**
* Return visibility
* @param {type.Model} elem
* @return {string}
*/
JavaCodeGenerator.prototype.getVisibility = function (elem) {
switch (elem.visibility) {
case UML.VK_PUBLIC:
return "public";
case UML.VK_PROTECTED:
return "protected";
case UML.VK_PRIVATE:
return "private";
}
return null;
};
/**
* Collect modifiers of a given element.
* @param {type.Model} elem
* @return {Array.}
*/
JavaCodeGenerator.prototype.getModifiers = function (elem) {
var modifiers = [];
var visibility = this.getVisibility(elem);
if (visibility) {
modifiers.push(visibility);
}
if (elem.isStatic === true) {
modifiers.push("static");
}
if (elem.isAbstract === true) {
modifiers.push("abstract");
}
if (elem.isFinalSpecialization === true || elem.isLeaf === true) {
modifiers.push("final");
}
if (elem.concurrency === UML.CCK_CONCURRENT) {
modifiers.push("synchronized");
}
// transient
// volatile
// strictfp
// const
// native
return modifiers;
};
/**
* Collect super classes of a given element
* @param {type.Model} elem
* @return {Array.}
*/
JavaCodeGenerator.prototype.getSuperClasses = function (elem) {
var generalizations = Repository.getRelationshipsOf(elem, function (rel) {
return (rel instanceof type.UMLGeneralization && rel.source === elem);
});
return _.map(generalizations, function (gen) { return gen.target; });
};
/**
* Collect super interfaces of a given element
* @param {type.Model} elem
* @return {Array.}
*/
JavaCodeGenerator.prototype.getSuperInterfaces = function (elem) {
var realizations = Repository.getRelationshipsOf(elem, function (rel) {
return (rel instanceof type.UMLInterfaceRealization && rel.source === elem);
});
return _.map(realizations, function (gen) { return gen.target; });
};
/**
* Return type expression
* @param {type.Model} elem
* @return {string}
*/
JavaCodeGenerator.prototype.getType = function (elem) {
var _type = "void";
// type name
if (elem instanceof type.UMLAssociationEnd) {
if (elem.reference instanceof type.UMLModelElement && elem.reference.name.length > 0) {
_type = elem.reference.name;
}
} else {
if (elem.type instanceof type.UMLModelElement && elem.type.name.length > 0) {
_type = elem.type.name;
} else if (_.isString(elem.type) && elem.type.length > 0) {
_type = elem.type;
}
}
// multiplicity
if (elem.multiplicity) {
if (_.contains(["0..*", "1..*", "*"], elem.multiplicity.trim())) {
if (elem.isOrdered === true) {
_type = "List<" + _type + ">";
} else {
_type = "Set<" + _type + ">";
}
} else if (elem.multiplicity !== "1" && elem.multiplicity.match(/^\d+$/)) { // number
_type += "[]";
}
}
return _type;
};
/**
* Write Doc
* @param {StringWriter} codeWriter
* @param {string} text
* @param {Object} options
*/
JavaCodeGenerator.prototype.writeDoc = function (codeWriter, text, options) {
var i, len, lines;
if (options.javaDoc && _.isString(text)) {
lines = text.trim().split("\n");
codeWriter.writeLine("/**");
for (i = 0, len = lines.length; i < len; i++) {
codeWriter.writeLine(" * " + lines[i]);
}
codeWriter.writeLine(" */");
}
};
/**
* Write Package Declaration
* @param {StringWriter} codeWriter
* @param {type.Model} elem
* @param {Object} options
*/
JavaCodeGenerator.prototype.writePackageDeclaration = function (codeWriter, elem, options) {
var path = null;
if (elem._parent) {
path = _.map(elem._parent.getPath(this.baseModel), function (e) { return e.name; }).join(".");
}
if (path) {
codeWriter.writeLine("package " + path + ";");
}
};
/**
* Write Constructor
* @param {StringWriter} codeWriter
* @param {type.Model} elem
* @param {Object} options
*/
JavaCodeGenerator.prototype.writeConstructor = function (codeWriter, elem, options) {
if (elem.name.length > 0) {
var terms = [];
// Doc
this.writeDoc(codeWriter, "Default constructor", options);
// Visibility
var visibility = this.getVisibility(elem);
if (visibility) {
terms.push(visibility);
}
terms.push(elem.name + "()");
codeWriter.writeLine(terms.join(" ") + " {");
codeWriter.writeLine("}");
}
};
/**
* Write Member Variable
* @param {StringWriter} codeWriter
* @param {type.Model} elem
* @param {Object} options
*/
JavaCodeGenerator.prototype.writeMemberVariable = function (codeWriter, elem, options) {
if (elem.name.length > 0) {
var terms = [];
// doc
this.writeDoc(codeWriter, elem.documentation, options);
// modifiers
var _modifiers = this.getModifiers(elem);
if (_modifiers.length > 0) {
terms.push(_modifiers.join(" "));
}
// type
terms.push(this.getType(elem));
// name
terms.push(elem.name);
// initial value
if (elem.defaultValue && elem.defaultValue.length > 0) {
terms.push("= " + elem.defaultValue);
}
codeWriter.writeLine(terms.join(" ") + ";");
}
};
/**
* Write Method
* @param {StringWriter} codeWriter
* @param {type.Model} elem
* @param {Object} options
* @param {boolean} skipBody
* @param {boolean} skipParams
*/
JavaCodeGenerator.prototype.writeMethod = function (codeWriter, elem, options, skipBody, skipParams) {
if (elem.name.length > 0) {
var terms = [];
var params = elem.getNonReturnParameters();
var returnParam = elem.getReturnParameter();
// doc
var doc = elem.documentation.trim();
//Erase Javadoc @param and @return
var i, lines = doc.split("\n");
doc = "";
for (i = 0, len = lines.length; i < len; i++) {
if(lines[i].lastIndexOf("@param", 0) !== 0 && lines[i].lastIndexOf("@return", 0) !== 0) {
doc += "\n" + lines[i];
}
}
_.each(params, function (param) {
doc += "\n@param " + param.name + " " + param.documentation;
});
if (returnParam) {
doc += "\n@return " + returnParam.documentation;
}
this.writeDoc(codeWriter, doc, options);
// modifiers
var _modifiers = this.getModifiers(elem);
if (_modifiers.length > 0) {
terms.push(_modifiers.join(" "));
}
// type
if (returnParam) {
terms.push(this.getType(returnParam));
} else {
terms.push("void");
}
// name + parameters
var paramTerms = [];
if (!skipParams) {
var i, len;
for (i = 0, len = params.length; i < len; i++) {
var p = params[i];
var s = this.getType(p) + " " + p.name;
if (p.isReadOnly === true) {
s = "final " + s;
}
paramTerms.push(s);
}
}
terms.push(elem.name + "(" + paramTerms.join(", ") + ")");
// body
if (skipBody === true || _.contains(_modifiers, "abstract")) {
codeWriter.writeLine(terms.join(" ") + ";");
} else {
codeWriter.writeLine(terms.join(" ") + " {");
codeWriter.indent();
codeWriter.writeLine("// TODO implement here");
// return statement
if (returnParam) {
var returnType = this.getType(returnParam);
if (returnType === "boolean") {
codeWriter.writeLine("return false;");
} else if (returnType === "int" || returnType === "long" || returnType === "short" || returnType === "byte") {
codeWriter.writeLine("return 0;");
} else if (returnType === "float") {
codeWriter.writeLine("return 0.0f;");
} else if (returnType === "double") {
codeWriter.writeLine("return 0.0d;");
} else if (returnType === "char") {
codeWriter.writeLine("return '0';");
} else if (returnType === "String") {
codeWriter.writeLine('return "";');
} else {
codeWriter.writeLine("return null;");
}
}
codeWriter.outdent();
codeWriter.writeLine("}");
}
}
};
/**
* Write Class
* @param {StringWriter} codeWriter
* @param {type.Model} elem
* @param {Object} options
*/
JavaCodeGenerator.prototype.writeClass = function (codeWriter, elem, options) {
var i, len, terms = [];
// Doc
var doc = elem.documentation.trim();
if (ProjectManager.getProject().author && ProjectManager.getProject().author.length > 0) {
doc += "\n@author " + ProjectManager.getProject().author;
}
this.writeDoc(codeWriter, doc, options);
// Modifiers
var _modifiers = this.getModifiers(elem);
if ( _.contains(_modifiers, "abstract") !== true && _.some(elem.operations, function (op) { return op.isAbstract === true; })) {
_modifiers.push("abstract");
}
if (_modifiers.length > 0) {
terms.push(_modifiers.join(" "));
}
// Class
terms.push("class");
terms.push(elem.name);
// Extends
var _extends = this.getSuperClasses(elem);
if (_extends.length > 0) {
terms.push("extends " + _extends[0].name);
}
// Implements
var _implements = this.getSuperInterfaces(elem);
if (_implements.length > 0) {
terms.push("implements " + _.map(_implements, function (e) { return e.name; }).join(", "));
}
codeWriter.writeLine(terms.join(" ") + " {");
codeWriter.writeLine();
codeWriter.indent();
// Constructor
this.writeConstructor(codeWriter, elem, options);
codeWriter.writeLine();
// Member Variables
// (from attributes)
for (i = 0, len = elem.attributes.length; i < len; i++) {
this.writeMemberVariable(codeWriter, elem.attributes[i], options);
codeWriter.writeLine();
}
// (from associations)
var associations = Repository.getRelationshipsOf(elem, function (rel) {
return (rel instanceof type.UMLAssociation);
});
for (i = 0, len = associations.length; i < len; i++) {
var asso = associations[i];
if (asso.end1.reference === elem && asso.end2.navigable === true) {
this.writeMemberVariable(codeWriter, asso.end2, options);
codeWriter.writeLine();
}
if (asso.end2.reference === elem && asso.end1.navigable === true) {
this.writeMemberVariable(codeWriter, asso.end1, options);
codeWriter.writeLine();
}
}
// Methods
for (i = 0, len = elem.operations.length; i < len; i++) {
this.writeMethod(codeWriter, elem.operations[i], options, false, false);
codeWriter.writeLine();
}
// Extends methods
if (_extends.length > 0) {
for (i = 0, len = _extends[0].operations.length; i < len; i++) {
_modifiers = this.getModifiers(_extends[0].operations[i]);
if( _.contains(_modifiers, "abstract") === true ) {
this.writeMethod(codeWriter, _extends[0].operations[i], options, false, false);
codeWriter.writeLine();
}
}
}
// Interface methods
for (var j = 0; j < _implements.length; j++) {
for (i = 0, len = _implements[j].operations.length; i < len; i++) {
this.writeMethod(codeWriter, _implements[j].operations[i], options, false, false);
codeWriter.writeLine();
}
}
// Inner Definitions
for (i = 0, len = elem.ownedElements.length; i < len; i++) {
var def = elem.ownedElements[i];
if (def instanceof type.UMLClass) {
if (def.stereotype === "annotationType") {
this.writeAnnotationType(codeWriter, def, options);
} else {
this.writeClass(codeWriter, def, options);
}
codeWriter.writeLine();
} else if (def instanceof type.UMLInterface) {
this.writeInterface(codeWriter, def, options);
codeWriter.writeLine();
} else if (def instanceof type.UMLEnumeration) {
this.writeEnum(codeWriter, def, options);
codeWriter.writeLine();
}
}
codeWriter.outdent();
codeWriter.writeLine("}");
};
/**
* Write Interface
* @param {StringWriter} codeWriter
* @param {type.Model} elem
* @param {Object} options
*/
JavaCodeGenerator.prototype.writeInterface = function (codeWriter, elem, options) {
var i, len, terms = [];
// Doc
this.writeDoc(codeWriter, elem.documentation, options);
// Modifiers
var visibility = this.getVisibility(elem);
if (visibility) {
terms.push(visibility);
}
// Interface
terms.push("interface");
terms.push(elem.name);
// Extends
var _extends = this.getSuperClasses(elem);
if (_extends.length > 0) {
terms.push("extends " + _.map(_extends, function (e) { return e.name; }).join(", "));
}
codeWriter.writeLine(terms.join(" ") + " {");
codeWriter.writeLine();
codeWriter.indent();
// Member Variables
// (from attributes)
for (i = 0, len = elem.attributes.length; i < len; i++) {
this.writeMemberVariable(codeWriter, elem.attributes[i], options);
codeWriter.writeLine();
}
// (from associations)
var associations = Repository.getRelationshipsOf(elem, function (rel) {
return (rel instanceof type.UMLAssociation);
});
for (i = 0, len = associations.length; i < len; i++) {
var asso = associations[i];
if (asso.end1.reference === elem && asso.end2.navigable === true) {
this.writeMemberVariable(codeWriter, asso.end2, options);
codeWriter.writeLine();
}
if (asso.end2.reference === elem && asso.end1.navigable === true) {
this.writeMemberVariable(codeWriter, asso.end1, options);
codeWriter.writeLine();
}
}
// Methods
for (i = 0, len = elem.operations.length; i < len; i++) {
this.writeMethod(codeWriter, elem.operations[i], options, true, false);
codeWriter.writeLine();
}
// Inner Definitions
for (i = 0, len = elem.ownedElements.length; i < len; i++) {
var def = elem.ownedElements[i];
if (def instanceof type.UMLClass) {
if (def.stereotype === "annotationType") {
this.writeAnnotationType(codeWriter, def, options);
} else {
this.writeClass(codeWriter, def, options);
}
codeWriter.writeLine();
} else if (def instanceof type.UMLInterface) {
this.writeInterface(codeWriter, def, options);
codeWriter.writeLine();
} else if (def instanceof type.UMLEnumeration) {
this.writeEnum(codeWriter, def, options);
codeWriter.writeLine();
}
}
codeWriter.outdent();
codeWriter.writeLine("}");
};
/**
* Write Enum
* @param {StringWriter} codeWriter
* @param {type.Model} elem
* @param {Object} options
*/
JavaCodeGenerator.prototype.writeEnum = function (codeWriter, elem, options) {
var i, len, terms = [];
// Doc
this.writeDoc(codeWriter, elem.documentation, options);
// Modifiers
var visibility = this.getVisibility(elem);
if (visibility) {
terms.push(visibility);
}
// Enum
terms.push("enum");
terms.push(elem.name);
codeWriter.writeLine(terms.join(" ") + " {");
codeWriter.indent();
// Literals
for (i = 0, len = elem.literals.length; i < len; i++) {
codeWriter.writeLine(elem.literals[i].name + (i < elem.literals.length - 1 ? "," : ""));
}
codeWriter.outdent();
codeWriter.writeLine("}");
};
/**
* Write AnnotationType
* @param {StringWriter} codeWriter
* @param {type.Model} elem
* @param {Object} options
*/
JavaCodeGenerator.prototype.writeAnnotationType = function (codeWriter, elem, options) {
var i, len, terms = [];
// Doc
var doc = elem.documentation.trim();
if (ProjectManager.getProject().author && ProjectManager.getProject().author.length > 0) {
doc += "\n@author " + ProjectManager.getProject().author;
}
this.writeDoc(codeWriter, doc, options);
// Modifiers
var _modifiers = this.getModifiers(elem);
if (_.contains(_modifiers, "abstract") !== true && _.some(elem.operations, function (op) { return op.isAbstract === true; })) {
_modifiers.push("abstract");
}
if (_modifiers.length > 0) {
terms.push(_modifiers.join(" "));
}
// AnnotationType
terms.push("@interface");
terms.push(elem.name);
codeWriter.writeLine(terms.join(" ") + " {");
codeWriter.writeLine();
codeWriter.indent();
// Member Variables
for (i = 0, len = elem.attributes.length; i < len; i++) {
this.writeMemberVariable(codeWriter, elem.attributes[i], options);
codeWriter.writeLine();
}
// Methods
for (i = 0, len = elem.operations.length; i < len; i++) {
this.writeMethod(codeWriter, elem.operations[i], options, true, true);
codeWriter.writeLine();
}
// Extends methods
if (_extends.length > 0) {
for (i = 0, len = _extends[0].operations.length; i < len; i++) {
_modifiers = this.getModifiers(_extends[0].operations[i]);
if(_.contains(_modifiers, "abstract") === true) {
this.writeMethod(codeWriter, _extends[0].operations[i], options, false, false);
codeWriter.writeLine();
}
}
}
// Inner Definitions
for (i = 0, len = elem.ownedElements.length; i < len; i++) {
var def = elem.ownedElements[i];
if (def instanceof type.UMLClass) {
if (def.stereotype === "annotationType") {
this.writeAnnotationType(codeWriter, def, options);
} else {
this.writeClass(codeWriter, def, options);
}
codeWriter.writeLine();
} else if (def instanceof type.UMLInterface) {
this.writeInterface(codeWriter, def, options);
codeWriter.writeLine();
} else if (def instanceof type.UMLEnumeration) {
this.writeEnum(codeWriter, def, options);
codeWriter.writeLine();
}
}
codeWriter.outdent();
codeWriter.writeLine("}");
};
/**
* Generate
* @param {type.Model} baseModel
* @param {string} basePath
* @param {Object} options
*/
function generate(baseModel, basePath, options) {
var result = new $.Deferred();
var javaCodeGenerator = new JavaCodeGenerator(baseModel, basePath);
return javaCodeGenerator.generate(baseModel, basePath, options);
}
exports.generate = generate;
});