You can not select more than 25 topics Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
tdesdk/umbrello/umbrello/codeimport/javaimport.cpp

550 lines
21 KiB

/***************************************************************************
* *
* This program is free software; you can redistribute it and/or modify *
* it under the terms of the GNU General Public License as published by *
* the Free Software Foundation; either version 2 of the License, or *
* (at your option) any later version. *
* *
* copyright (C) 2006-2007 *
* Umbrello UML Modeller Authors <uml-devel@uml.sf.net> *
***************************************************************************/
// own header
#include "javaimport.h"
// qt/kde includes
#include <tqfile.h>
#include <tqtextstream.h>
#include <tqstringlist.h>
#include <tqregexp.h>
#include <kdebug.h>
// app includes
#include "import_utils.h"
#include "../uml.h"
#include "../umldoc.h"
#include "../umlpackagelist.h"
#include "../package.h"
#include "../classifier.h"
#include "../enum.h"
#include "../operation.h"
#include "../attribute.h"
TQStringList JavaImport::s_filesAlreadyParsed;
int JavaImport::s_parseDepth = 0;
JavaImport::JavaImport() : NativeImportBase("//") {
setMultiLineComment("/*", "*/");
initVars();
}
JavaImport::~JavaImport() {
}
void JavaImport::initVars() {
m_isStatic = false;
}
/// Catenate possible template arguments/array dimensions to the end of the type name.
TQString JavaImport::joinTypename(TQString typeName) {
if (m_source[m_srcIndex + 1] == "<" ||
m_source[m_srcIndex + 1] == "[") {
uint start = ++m_srcIndex;
if (! skipToClosing(m_source[start][0]))
return typeName;
for (uint i = start; i <= m_srcIndex; i++) {
typeName += m_source[i];
}
}
// to handle multidimensional arrays, call recursively
if (m_source[m_srcIndex + 1] == "[") {
typeName = joinTypename( typeName );
}
return typeName;
}
void JavaImport::fillSource(const TQString& word) {
TQString lexeme;
const uint len = word.length();
for (uint i = 0; i < len; i++) {
const TQChar& c = word[i];
if (c.isLetterOrNumber() || c == '_' || c == '.') {
lexeme += c;
} else {
if (!lexeme.isEmpty()) {
m_source.append(lexeme);
lexeme = TQString();
}
m_source.append(TQString(c));
}
}
if (!lexeme.isEmpty())
m_source.append(lexeme);
}
///Spawn off an import of the specified file
void JavaImport::spawnImport( TQString file ) {
// if the file is being parsed, don't bother
//
if (s_filesAlreadyParsed.contains( file ) ) {
return;
}
if (TQFile::exists(file)) {
JavaImport importer;
TQStringList fileList;
fileList.append( file );
s_filesAlreadyParsed.append( file );
importer.importFiles( fileList );
}
}
///returns the UML Object if found, or null otherwise
UMLObject* findObject( TQString name, UMLPackage *parentPkg ) {
UMLDoc *umldoc = UMLApp::app()->getDocument();
UMLObject * o = umldoc->findUMLObject(name, Uml::ot_UMLObject , parentPkg);
return o;
}
///Resolve the specified className
UMLObject* JavaImport::resolveClass (TQString className) {
kDebug() << "importJava trying to resolve " << className << endl;
// keep track if we are dealing with an array
//
bool isArray = className.contains('[');
// remove any [] so that the class itself can be resolved
//
TQString baseClassName = className;
baseClassName.remove('[');
baseClassName.remove(']');
// java has a few implicit imports. Most relevant for this is the
// current package, which is in the same directory as the current file
// being parsed
//
TQStringList file = TQStringList::split( '/', m_currentFileName);
// remove the filename. This leaves the full path to the containing
// dir which should also include the package hierarchy
//
file.pop_back();
// the file we're looking for might be in the same directory as the
// current class
//
TQString myDir = file.join( "/" );
TQString myFile = '/' + myDir + '/' + baseClassName + ".java";
if ( TQFile::exists(myFile) ) {
spawnImport( myFile );
if ( isArray ) {
// we have imported the type. For arrays we want to return
// the array type
return Import_Utils::createUMLObject(Uml::ot_Class, className, m_scope[m_scopeIndex]);
}
return findObject(baseClassName, m_scope[m_scopeIndex]);
}
// the class we want is not in the same package as the one being imported.
// use the imports to find the one we want.
//
TQStringList package = TQStringList::split( '.', m_currentPackage);
int dirsInPackageCount = package.size();
for (int count=0; count < dirsInPackageCount; count ++ ) {
// pop off one by one the directories, until only the source root remains
//
file.pop_back();
}
// this is now the root of any further source imports
TQString sourceRoot = '/' + file.join("/") + '/';
for (TQStringList::Iterator pathIt = m_imports.begin();
pathIt != m_imports.end(); ++pathIt) {
TQString import = (*pathIt);
TQStringList split = TQStringList::split( '.', import );
split.pop_back(); // remove the * or the classname
if ( import.endsWith( "*" ) || import.endsWith( baseClassName) ) {
// check if the file we want is in this imported package
// convert the org.test type package into a filename
//
TQString aFile = sourceRoot + split.join("/") + '/' + baseClassName + ".java";
if ( TQFile::exists(aFile) ) {
spawnImport( aFile );
// we need to set the package for the class that will be resolved
// start at the root package
UMLPackage *parent = m_scope[0];
UMLPackage *current = NULL;
for (TQStringList::Iterator it = split.begin(); it != split.end(); ++it) {
TQString name = (*it);
UMLObject *ns = Import_Utils::createUMLObject(Uml::ot_Package,
name, parent);
current = static_cast<UMLPackage*>(ns);
parent = current;
} // for
if ( isArray ) {
// we have imported the type. For arrays we want to return
// the array type
return Import_Utils::createUMLObject(Uml::ot_Class, className, current);
}
// now that we have the right package, the class should be findable
return findObject(baseClassName, current);
} // if file exists
} // if import matches
} //foreach import
return NULL; // no match
}
/// keep track of the current file being parsed and reset the list of imports
void JavaImport::parseFile(const TQString& filename) {
m_currentFileName= filename;
m_imports.clear();
// default visibility is Impl, unless we are an interface, then it is
// public for member vars and methods
m_defaultCurrentAccess = Uml::Visibility::Implementation;
m_currentAccess = m_defaultCurrentAccess;
s_parseDepth++;
// in the case of self referencing types, we can avoid parsing the
// file twice by adding it to the list
s_filesAlreadyParsed.append(filename);
NativeImportBase::parseFile(filename);
s_parseDepth--;
if ( s_parseDepth <= 0 ) {
// if the user decides to clear things out and reparse, we need
// to honor the request, so reset things for next time.
s_filesAlreadyParsed.clear();
s_parseDepth = 0;
}
}
bool JavaImport::parseStmt() {
const uint srcLength = m_source.count();
const TQString& keyword = m_source[m_srcIndex];
//kDebug() << '"' << keyword << '"' << endl;
if (keyword == "package") {
m_currentPackage = advance();
const TQString& qualifiedName = m_currentPackage;
TQStringList names = TQStringList::split(".", qualifiedName);
for (TQStringList::Iterator it = names.begin(); it != names.end(); ++it) {
TQString name = (*it);
UMLObject *ns = Import_Utils::createUMLObject(Uml::ot_Package,
name, m_scope[m_scopeIndex], m_comment);
m_scope[++m_scopeIndex] = static_cast<UMLPackage*>(ns);
}
if (advance() != ";") {
kError() << "importJava: unexpected: " << m_source[m_srcIndex] << endl;
skipStmt();
}
return true;
}
if (keyword == "class" || keyword == "interface") {
const TQString& name = advance();
const Uml::Object_Type t = (keyword == "class" ? Uml::ot_Class : Uml::ot_Interface);
UMLObject *ns = Import_Utils::createUMLObject(t, name, m_scope[m_scopeIndex], m_comment);
m_scope[++m_scopeIndex] = m_klass = static_cast<UMLClassifier*>(ns);
m_klass->setAbstract(m_isAbstract);
m_klass->setStatic(m_isStatic);
m_klass->setVisibility(m_currentAccess);
// The UMLObject found by createUMLObject might originally have been created as a
// placeholder with a type of class but if is really an interface, then we need to
// change it.
Uml::Object_Type ot = (keyword == "interface" ? Uml::ot_Interface : Uml::ot_Class);
m_klass->setBaseType(ot);
m_isAbstract = m_isStatic = false;
// if no modifier is specified in an interface, then it means public
if ( m_klass->isInterface() ) {
m_defaultCurrentAccess = Uml::Visibility::Public;
}
if (advance() == ";") // forward declaration
return true;
if (m_source[m_srcIndex] == "<") {
// template args - preliminary, rudimentary implementation
// @todo implement all template arg syntax
uint start = m_srcIndex;
if (! skipToClosing('<')) {
kError() << "importJava(" << name << "): template syntax error" << endl;
return false;
}
while (1) {
const TQString arg = m_source[++start];
if (! arg.contains( TQRegExp("^[A-Za-z_]") )) {
kDebug() << "importJava(" << name << "): cannot handle template syntax ("
<< arg << ")" << endl;
break;
}
/* UMLTemplate *tmpl = */ m_klass->addTemplate(arg);
const TQString next = m_source[++start];
if (next == ">")
break;
if (next != ",") {
kDebug() << "importJava(" << name << "): can't handle template syntax ("
<< next << ")" << endl;
break;
}
}
advance(); // skip over ">"
}
if (m_source[m_srcIndex] == "extends") {
const TQString& baseName = advance();
// try to resolve the class we are extending, or if impossible
// create a placeholder
UMLObject *parent = resolveClass( baseName );
if ( parent ) {
Import_Utils::createGeneralization(m_klass, static_cast<UMLClassifier*>(parent));
} else {
kDebug() << "importJava parentClass " << baseName
<< " is not resolveable. Creating placeholder" << endl;
Import_Utils::createGeneralization(m_klass, baseName);
}
advance();
}
if (m_source[m_srcIndex] == "implements") {
while (m_srcIndex < srcLength - 1 && advance() != "{") {
const TQString& baseName = m_source[m_srcIndex];
// try to resolve the interface we are implementing, if this fails
// create a placeholder
UMLObject *interface = resolveClass( baseName );
if ( interface ) {
Import_Utils::createGeneralization(m_klass, static_cast<UMLClassifier*>(interface));
} else {
kDebug() << "importJava implementing interface "<< baseName
<<" is not resolvable. Creating placeholder" <<endl;
Import_Utils::createGeneralization(m_klass, baseName);
}
if (advance() != ",")
break;
}
}
if (m_source[m_srcIndex] != "{") {
kError() << "importJava: ignoring excess chars at " << name
<< " (" << m_source[m_srcIndex] << ")" << endl;
skipStmt("{");
}
return true;
}
if (keyword == "enum") {
const TQString& name = advance();
UMLObject *ns = Import_Utils::createUMLObject(Uml::ot_Enum,
name, m_scope[m_scopeIndex], m_comment);
UMLEnum *enumType = static_cast<UMLEnum*>(ns);
skipStmt("{");
while (m_srcIndex < srcLength - 1 && advance() != "}") {
Import_Utils::addEnumLiteral(enumType, m_source[m_srcIndex]);
TQString next = advance();
if (next == "{" || next == "(") {
if (! skipToClosing(next[0]))
return false;
next = advance();
}
if (next != ",") {
if (next == ";") {
// @todo handle methods in enum
// For now, we cheat (skip them)
m_source[m_srcIndex] = "{";
if (! skipToClosing('{'))
return false;
}
break;
}
}
return true;
}
if (keyword == "static") {
m_isStatic = true;
return true;
}
// if we detected static previously and keyword is { then this is a static block
if (m_isStatic && keyword == "{") {
// reset static flag and jump to end of static block
m_isStatic = false;
return skipToClosing('{');
}
if (keyword == "abstract") {
m_isAbstract = true;
return true;
}
if (keyword == "public") {
m_currentAccess = Uml::Visibility::Public;
return true;
}
if (keyword == "protected") {
m_currentAccess = Uml::Visibility::Protected;
return true;
}
if (keyword == "private") {
m_currentAccess = Uml::Visibility::Private;
return true;
}
if (keyword == "final" ||
keyword == "native" ||
keyword == "synchronized" ||
keyword == "transient" ||
keyword == "volatile") {
//@todo anything to do here?
return true;
}
if (keyword == "import") {
// keep track of imports so we can resolve classes we are dependent on
TQString import = advance();
if ( import.endsWith(".") ) {
//this most likely an import that ends with a *
//
import = import + advance();
}
m_imports.append( import );
// move past ;
skipStmt();
return true;
}
if (keyword == "@") { // annotation
advance();
if (m_source[m_srcIndex + 1] == "(") {
advance();
skipToClosing('(');
}
return true;
}
if (keyword == "}") {
if (m_scopeIndex)
m_klass = dynamic_cast<UMLClassifier*>(m_scope[--m_scopeIndex]);
else
kError() << "importJava: too many }" << endl;
return true;
}
// At this point, we expect `keyword' to be a type name
// (of a member of class or interface, or return type
// of an operation.) Up next is the name of the attribute
// or operation.
if (! keyword.contains( TQRegExp("^\\w") )) {
kError() << "importJava: ignoring " << keyword << endl;
return false;
}
TQString typeName = m_source[m_srcIndex];
typeName = joinTypename(typeName);
// At this point we need a class.
if (m_klass == NULL) {
kError() << "importJava: no class set for " << typeName << endl;
return false;
}
TQString name = advance();
TQString nextToken;
if (typeName == m_klass->getName() && name == "(") {
// Constructor.
nextToken = name;
name = typeName;
typeName = TQString();
} else {
nextToken = advance();
}
if (name.contains( TQRegExp("\\W") )) {
kError() << "importJava: expecting name in " << name << endl;
return false;
}
if (nextToken == "(") {
// operation
UMLOperation *op = Import_Utils::makeOperation(m_klass, name);
m_srcIndex++;
while (m_srcIndex < srcLength && m_source[m_srcIndex] != ")") {
TQString typeName = m_source[m_srcIndex];
if ( typeName == "final" || typeName.startsWith( "//") ) {
// ignore the "final" keyword and any comments in method args
typeName = advance();
}
typeName = joinTypename(typeName);
TQString parName = advance();
// the Class might not be resolved yet so resolve it if necessary
UMLObject *obj = resolveClass(typeName);
if (obj) {
// by prepending the package, unwanted placeholder types will not get created
typeName = obj->getFullyQualifiedName(".");
}
/* UMLAttribute *att = */ Import_Utils::addMethodParameter(op, typeName, parName);
if (advance() != ",")
break;
m_srcIndex++;
}
// before adding the method, try resolving the return type
UMLObject *obj = resolveClass(typeName);
if (obj) {
// using the fully qualified name means that a placeholder type will not be created.
typeName = obj->getFullyQualifiedName(".");
}
Import_Utils::insertMethod(m_klass, op, m_currentAccess, typeName,
m_isStatic, m_isAbstract, false /*isFriend*/,
false /*isConstructor*/, m_comment);
m_isAbstract = m_isStatic = false;
// reset the default visibility
m_currentAccess = m_defaultCurrentAccess;
// At this point we do not know whether the method has a body or not.
do {
nextToken = advance();
} while (nextToken != "{" && nextToken != ";");
if (nextToken == ";") {
// No body (interface or abstract)
return true;
} else {
return skipToClosing('{');
}
}
// At this point we know it's some kind of attribute declaration.
while (1) {
while (nextToken != "," && nextToken != ";") {
if (nextToken == "=") {
if ((nextToken = advance()) == "new") {
advance();
if ((nextToken = advance()) == "(") {
skipToClosing('(');
if ((nextToken = advance()) == "{") {
skipToClosing('{');
} else {
skipStmt();
break;
}
} else {
skipStmt();
break;
}
} else {
skipStmt();
break;
}
} else {
name += nextToken; // add possible array dimensions to `name'
}
nextToken = advance();
}
// try to resolve the class type, or create a placeholder if that fails
UMLObject *type = resolveClass( typeName );
UMLObject *o;
if (type) {
o = Import_Utils::insertAttribute(m_klass, m_currentAccess, name,
static_cast<UMLClassifier*>(type), m_comment, m_isStatic);
} else {
o = Import_Utils::insertAttribute(m_klass, m_currentAccess, name,
typeName, m_comment, m_isStatic);
}
// UMLAttribute *attr = static_cast<UMLAttribute*>(o);
if (nextToken != ",") {
// reset the modifiers
m_isStatic = m_isAbstract = false;
break;
}
name = advance();
nextToken = advance();
}
// reset visibility to default
m_currentAccess = m_defaultCurrentAccess;
if (m_source[m_srcIndex] != ";") {
kError() << "importJava: ignoring trailing items at " << name << endl;
skipStmt();
}
return true;
}