Modeler.java
/* $Id$
*****************************************************************************
* Copyright (c) 2009-2012 Contributors - see below
* All rights reserved. This program and the accompanying materials
* are made available under the terms of the Eclipse Public License v1.0
* which accompanies this distribution, and is available at
* http://www.eclipse.org/legal/epl-v10.html
*
* Contributors:
* tfmorris
*****************************************************************************
*
* Some portions of this file was previously release using the BSD License:
*/
// Copyright (c) 2003-2008 The Regents of the University of California. All
// Rights Reserved. Permission to use, copy, modify, and distribute this
// software and its documentation without fee, and without a written
// agreement is hereby granted, provided that the above copyright notice
// and this paragraph appear in all copies. This software program and
// documentation are copyrighted by The Regents of the University of
// California. The software program and documentation are supplied "AS
// IS", without any accompanying services from The Regents. The Regents
// does not warrant that the operation of the program will be
// uninterrupted or error-free. The end-user understands that the program
// was developed for research purposes and is advised not to rely
// exclusively on the program for any reason. IN NO EVENT SHALL THE
// UNIVERSITY OF CALIFORNIA BE LIABLE TO ANY PARTY FOR DIRECT, INDIRECT,
// SPECIAL, INCIDENTAL, OR CONSEQUENTIAL DAMAGES, INCLUDING LOST PROFITS,
// ARISING OUT OF THE USE OF THIS SOFTWARE AND ITS DOCUMENTATION, EVEN IF
// THE UNIVERSITY OF CALIFORNIA HAS BEEN ADVISED OF THE POSSIBILITY OF
// SUCH DAMAGE. THE UNIVERSITY OF CALIFORNIA SPECIFICALLY DISCLAIMS ANY
// WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF
// MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE. THE SOFTWARE
// PROVIDED HEREUNDER IS ON AN "AS IS" BASIS, AND THE UNIVERSITY OF
// CALIFORNIA HAS NO OBLIGATIONS TO PROVIDE MAINTENANCE, SUPPORT,
// UPDATES, ENHANCEMENTS, OR MODIFICATIONS.
package org.argouml.uml.reveng.idl;
import java.util.ArrayList;
import java.util.Collection;
import java.util.Collections;
import java.util.HashSet;
import java.util.Hashtable;
import java.util.Iterator;
import java.util.List;
import java.util.Stack;
import java.util.logging.Level;
import java.util.logging.Logger;
import org.argouml.application.api.Argo;
import org.argouml.kernel.ProjectManager;
import org.argouml.model.CoreFactory;
import org.argouml.model.Facade;
import org.argouml.model.Model;
import org.argouml.uml.reveng.ImportCommon;
import org.argouml.uml.reveng.ImportInterface;
/**
* Modeler maps IDL source code(parsed/recognised by ANTLR) to UML model
* elements.
* <p>
* Cloned from the Java modeler which it used to depend on and only lightly
* modified. Much of this machinery is unneeded and can be deleted when someone
* has the chance. - tfm
*
* @author Marcus Andersson
* @author Tom Morris
*/
class Modeler {
private static final Logger LOG =
Logger.getLogger(Modeler.class.getName());
private static final String DEFAULT_PACKAGE = "default";
private static final List<String> EMPTY_STRING_LIST =
Collections.emptyList();
/**
* Current working model.
*/
private Object model;
/**
* Current import settings.
*/
private ImportCommon importSession;
/**
* The package which the currentClassifier belongs to.
*/
private Object currentPackage;
/**
* Keeps the data that varies during parsing.
*/
private ParseState parseState;
/**
* Stack up the state when descending inner classes.
*/
private Stack<ParseState> parseStateStack;
/**
* Only attributes will be generated. Setting currently unused. Left over
* from Java modeler. May be useful if/when support for attributes is
* implemented.
*/
private boolean noAssociations = false;
/**
* Arrays will be modeled as unique datatypes. Setting currently
* unused. Left over from Java modeler. May be useful if/when support
* for attributes is implemented.
*/
private boolean arraysAsDatatype = false;
/**
* The name of the file being parsed.
*/
private String fileName;
/**
* Arbitrary attributes.
*/
private Hashtable<String, Object> attributes =
new Hashtable<String, Object>();
/**
* List of the names of parsed method calls.
*/
private List<String> methodCalls = new ArrayList<String>();
/**
* HashMap of parsed local variables. Indexed by variable name with string
* representation of the type stored as the value.
*/
private Hashtable<String, String> localVariables =
new Hashtable<String, String>();
/**
* New model elements that were created during this
* reverse engineering session.
* TODO: We want a stronger type here, but ArgoUML treats all elements
* as just simple Objects.
*/
private Collection<Object> newElements;
/**
* Flag to control generation of artificial names for associations. If
* true, generate names of form "From->To". If false, set name to null.
*/
private boolean generateNames = true;
/**
* Create a new modeller.
*
* @param theModel The model to work with.
* @param theFileName the current file name
*/
Modeler(Object theModel, String theFileName) {
model = theModel;
noAssociations = false;
arraysAsDatatype = false;
currentPackage = this.model;
newElements = new HashSet<Object>();
parseState = new ParseState(this.model, getPackage(DEFAULT_PACKAGE));
parseStateStack = new Stack<ParseState>();
fileName = theFileName;
}
/**
* @param key the key of the attribute to get
* @return the value of the attribute
*/
public Object getAttribute(String key) {
return attributes.get(key);
}
/**
* @param key the key of the attribute
* @param value the value for the attribute
*/
public void setAttribute(String key, Object value) {
attributes.put(key, value);
}
/**
* This is a mapping from a compilation Unit -> a UML component.
* Classes are resident in a component.
* Imports are relationships between components and other classes
* / packages.<p>
*
* See JSR 26.<p>
*
* Adding components is a little messy since there are 2 cases:
*
* <ol>
* <li>source file has package statement, will be added several times
* since lookup in addComponent() only looks in the model since the
* package namespace is not yet known.
*
* <li>source file has not package statement: component is added
* to the model namespace. the is no package statement so the
* lookup will always work.
*
* </ol>
* Therefore in the case of (1), we need to delete duplicate components
* in the addPackage() method.<p>
*
* In either case we need to add a package since we don't know in advance
* if there will be a package statement.<p>
*/
public void addComponent() {
// try and find the component in the current package
// to cope with repeated imports
// [this will never work if a package statment exists:
// because the package statement is parsed after the component is
// identified]
Object component = Model.getFacade().lookupIn(currentPackage, fileName);
if (component == null) {
component = Model.getCoreFactory().createComponent();
Model.getCoreHelper().setName(component, fileName);
newElements.add(component);
}
parseState.addComponent(component);
// set the namespace of the component, in the event
// that the source file does not have a package stmt
Model.getCoreHelper().setNamespace(parseState.getComponent(), model);
}
/**
* Called from the parser when a package clause is found.
*
* @param name The name of the package.
*/
public void addPackage(String name) {
// Add a package figure for this package to the owner's class
// diagram, if it's not in the diagram yet. I do this for all
// the class diagrams up to the top level, since I need
// diagrams for all the packages.
String ownerPackageName, currentName = name;
ownerPackageName = getPackageName(currentName);
while (!"".equals(ownerPackageName)) {
currentName = ownerPackageName;
ownerPackageName = getPackageName(currentName);
}
// Save src_path in the upper package
// TODO: Rework this so that we don't need importSession here.
// perhaps move to the common import code. - tfm
Object mPackage = getPackage(currentName);
if (importSession != null && importSession.getSrcPath() != null
&& Model.getFacade().getTaggedValue(mPackage,
ImportInterface.SOURCE_PATH_TAG) == null) {
Model.getCoreHelper()
.setTaggedValue(mPackage, ImportInterface.SOURCE_PATH_TAG,
importSession.getSrcPath());
}
// Find or create a Package model element for this package.
mPackage = getPackage(name);
// Set the current package for the following source code.
currentPackage = mPackage;
parseState.addPackageContext(mPackage);
// Delay diagram creation until any classifier (class or
// interface) will be found
//set the namespace of the component
// check to see if there is already a component defined:
Object component = Model.getFacade().lookupIn(currentPackage, fileName);
if (component == null) {
// set the namespace of the component
Model.getCoreHelper().setNamespace(
parseState.getComponent(),
currentPackage);
} else {
// a component already exists,
// so delete the latest one(the duplicate)
Object oldComponent = parseState.getComponent();
Model.getUmlFactory().delete(oldComponent);
newElements.remove(oldComponent);
// change the parse state to the existing one.
parseState.addComponent(component);
}
}
/**
* Called from the parser when an import clause is found.
*
* @param name The name of the import. Can end with a '*'.
*/
public void addImport(String name) {
addImport(name, false);
}
/**
* Called from the parser when an import clause is found.
*
* @param name The name of the import. Can end with a '*'.
* @param forceIt Force addition by creating all that's missing.
*/
void addImport(String name, boolean forceIt) {
// only do imports on the 2nd pass.
if (getLevel() == 0) {
return;
}
String packageName = getPackageName(name);
// TODO: In the case of an inner class, we probably want either the
// qualified name with both outer and inner class names, or just the
// outer class name
String classifierName = getClassifierName(name);
Object mPackage = getPackage(packageName);
// import on demand
if (classifierName.equals("*")) {
parseState.addPackageContext(mPackage);
Object srcFile = parseState.getComponent();
buildImport(mPackage, srcFile);
}
// single type import
else {
Object mClassifier = null;
try {
mClassifier =
(new PackageContext(null, mPackage)).get(classifierName);
} catch (ClassifierNotFoundException e) {
if (forceIt && classifierName != null && mPackage != null) {
// we must guess if it's a class or an interface, so: class
LOG.log(Level.INFO,
"Modeler.java: "
+ "forced creation of unknown classifier "
+ classifierName);
// TODO: A better strategy would be to defer creating this
// until we have enough information to determine what it is
mClassifier = Model.getCoreFactory().buildClass(
classifierName, mPackage);
newElements.add(mClassifier);
} else {
warnClassifierNotFound(classifierName,
"an imported classifier");
}
}
if (mClassifier != null) {
parseState.addClassifierContext(mClassifier);
Object srcFile = parseState.getComponent();
buildImport(mClassifier, srcFile);
}
}
}
private static final String IMPORT_STEREOTYPE = "idlImport";
/*
* Build an IDL import equivalent in UML. We use a Dependency with the
* stereotype <<idlImport>>.
*/
private Object buildImport(Object element, Object srcFile) {
// Look for an existing dependency and return it if found
Collection dependencies = Model.getCoreHelper().getDependencies(
element, srcFile);
for (Object dep : dependencies) {
if (Model.getExtensionMechanismsHelper().hasStereotype(dep,
IMPORT_STEREOTYPE)) {
return dep;
}
}
// Not found. Create a new one.
Object pkgImport = Model.getCoreFactory().buildDependency(srcFile,
element);
Model.getCoreHelper().addStereotype(pkgImport,
getStereotype(IMPORT_STEREOTYPE));
String newName = makeFromToName(srcFile, element);
Model.getCoreHelper().setName(pkgImport, newName);
newElements.add(pkgImport);
return pkgImport;
}
private String makeAbstractionName(Object child, Object parent) {
return makeFromToName(child, parent);
}
private String makeAssociationName(Object from, Object to) {
return makeFromToName(from, to);
}
private String makeFromToName(Object from, Object to) {
if (!generateNames ) {
return null;
} else {
return makeFromToName(
Model.getFacade().getName(from),
Model.getFacade().getName(to));
}
}
private String makeFromToName(String from, String to) {
if (!generateNames) {
return null;
} else {
// TODO: This isn't localized, but I'm not sure it can be
// without other side effects - tfm - 20070410
return from + " -> " + to;
}
}
/**
* Called from the parser when a class declaration is found.
*
* @param name The name of the class.
* @param modifiers A sequence of class modifiers.
* @param superclassName Zero or one string with the name of the
* superclass. Can be fully qualified or
* just a simple class name.
* @param interfaces Zero or more strings with the names of implemented
* interfaces. Can be fully qualified or just a
* simple interface name.
* @param javadoc The javadoc comment. null or "" if no comment available.
*/
public void addClass(String name,
short modifiers,
String superclassName,
List<String> interfaces,
String javadoc) {
addClass(name, modifiers, EMPTY_STRING_LIST, superclassName,
interfaces, javadoc, false);
}
/**
* Called from the parser when a class declaration is found.
*
* @param name The name of the class.
* @param modifiers A bitmask of class modifiers.
* @param typeParameters List of strings containing names of types for
* parameters
* @param superclassName Zero or one string with the name of the superclass.
* Can be fully qualified or just a simple class name.
* @param interfaces Zero or more strings with the names of implemented
* interfaces. Can be fully qualified or just a simple
* interface name.
* @param javadoc The javadoc comment. null or "" if no comment available.
* @param forceIt Force addition by creating all that's missing.
*/
void addClass(String name,
short modifiers,
List<String> typeParameters,
String superclassName,
List<String> interfaces,
String javadoc,
boolean forceIt) {
if (typeParameters != null && typeParameters.size() > 0) {
logError("type parameters not supported on Class",
name);
}
Object mClass =
addClassifier(Model.getCoreFactory().createClass(),
name, modifiers, javadoc, typeParameters);
// Model.getCoreHelper().setAbstract(
// mClass,
// (modifiers & IDLParser.ACC_ABSTRACT) > 0);
// Model.getCoreHelper().setLeaf(
// mClass,
// (modifiers & IDLParser.ACC_FINAL) > 0);
Model.getCoreHelper().setRoot(mClass, false);
newElements.add(mClass);
// only do generalizations and realizations on the 2nd pass.
if (getLevel() == 0) {
return;
}
if (superclassName != null) {
Object parentClass = null;
try {
parentClass =
getContext(superclassName)
.get(getClassifierName(superclassName));
getGeneralization(currentPackage, parentClass, mClass);
} catch (ClassifierNotFoundException e) {
if (forceIt && superclassName != null && model != null) {
LOG.log(Level.INFO,
"Modeler.java: forced creation of unknown class "
+ superclassName);
String packageName = getPackageName(superclassName);
String classifierName = getClassifierName(superclassName);
Object mPackage = (packageName.length() > 0)
? getPackage(packageName)
: model;
parentClass = Model.getCoreFactory().buildClass(
classifierName, mPackage);
newElements.add(parentClass);
getGeneralization(currentPackage, parentClass, mClass);
} else {
warnClassifierNotFound(superclassName,
"a generalization");
}
}
}
if (interfaces != null) {
addInterfaces(mClass, interfaces, forceIt);
}
}
/**
* Called from the parser when an anonymous inner class is found.
*
* @param type The type of this anonymous class.
*/
public void addAnonymousClass(String type) {
addAnonymousClass(type, false);
}
/**
* Called from the parser when an anonymous inner class is found.
*
* @param type The type of this anonymous class.
* @param forceIt Force addition by creating all that's missing.
*/
void addAnonymousClass(String type, boolean forceIt) {
String name = parseState.anonymousClass();
try {
Object mClassifier = getContext(type).get(getClassifierName(type));
List<String> interfaces = new ArrayList<String>();
if (Model.getFacade().isAInterface(mClassifier)) {
interfaces.add(type);
}
addClass(name,
(short) 0,
EMPTY_STRING_LIST,
Model.getFacade().isAClass(mClassifier) ? type : null,
interfaces,
"",
forceIt);
} catch (ClassifierNotFoundException e) {
// Must add it anyway, or the class popping will mismatch.
addClass(name, (short) 0, EMPTY_STRING_LIST, null,
EMPTY_STRING_LIST, "", forceIt);
LOG.log(Level.INFO,
"Modeler.java: an anonymous class was created "
+ "although it could not be found in the classpath.");
}
}
/**
* Add an Interface to the model.
*
* TODO: This method preserves the historical public API which is used by
* other reverse engineering modules such as the Classfile module. This
* really needs to be decoupled.
*
* @param name
* The name of the interface.
* @param modifiers
* A sequence of interface modifiers.
* @param interfaces
* Zero or more strings with the names of extended interfaces.
* Can be fully qualified or just a simple interface name.
* @param javadoc
* The javadoc comment. "" if no comment available.
*/
public void addInterface(String name,
short modifiers,
List<String> interfaces,
String javadoc) {
addInterface(name, modifiers, EMPTY_STRING_LIST, interfaces,
javadoc, false);
}
/**
* Called from the parser when an interface declaration is found.
*
* @param name The name of the interface.
* @param modifiers A sequence of interface modifiers.
* @param interfaces Zero or more strings with the names of extended
* interfaces. Can be fully qualified or just a simple interface name.
* @param javadoc The javadoc comment. "" if no comment available.
* @param forceIt Force addition by creating all that's missing.
*/
void addInterface(String name,
short modifiers,
List<String> typeParameters,
List<String> interfaces,
String javadoc,
boolean forceIt) {
if (typeParameters != null && typeParameters.size() > 0) {
logError("type parameters not supported on Interface",
name);
}
Object mInterface =
addClassifier(Model.getCoreFactory().createInterface(),
name,
modifiers,
javadoc,
typeParameters);
// only do generalizations and realizations on the 2nd pass.
if (getLevel() == 0) {
return;
}
for (String interfaceName : interfaces) {
Object parentInterface = null;
try {
parentInterface =
getContext(interfaceName)
.getInterface(getClassifierName(interfaceName));
getGeneralization(currentPackage, parentInterface, mInterface);
} catch (ClassifierNotFoundException e) {
if (forceIt && interfaceName != null && model != null) {
LOG.log(Level.INFO,
"Modeler.java: "
+ "forced creation of unknown interface "
+ interfaceName);
String packageName = getPackageName(interfaceName);
String classifierName = getClassifierName(interfaceName);
Object mPackage = (packageName.length() > 0)
? getPackage(packageName)
: model;
parentInterface = Model.getCoreFactory().buildInterface(
classifierName, mPackage);
newElements.add(parentInterface);
getGeneralization(currentPackage, parentInterface,
mInterface);
} else {
warnClassifierNotFound(interfaceName,
"a generalization");
}
}
}
}
/**
* Called from the parser when an enumeration declaration is found.
*
* @param name The name of the class.
* @param modifiers A sequence of class modifiers.
* @param interfaces Zero or more strings with the names of implemented
* interfaces. Can be fully qualified or just a
* simple interface name.
* @param javadoc The javadoc comment. null or "" if no comment available.
* @param forceIt Force addition by creating all that's missing.
*/
void addEnumeration(String name,
short modifiers,
List<String> interfaces,
String javadoc,
boolean forceIt) {
Object mClass =
addClassifier(Model.getCoreFactory().createClass(),
name, modifiers, javadoc,
EMPTY_STRING_LIST); // no type params for now
Model.getCoreHelper().addStereotype(
mClass,
getStereotype("enumeration"));
// if ((modifiers & IDLParser.ACC_ABSTRACT) > 0) {
// // abstract enums are illegal in Java
// logError("Illegal \"abstract\" modifier on enum ", name);
// } else {
// Model.getCoreHelper().setAbstract(mClass, false);
// }
// if ((modifiers & IDLParser.ACC_FINAL) > 0) {
// // it's an error to explicitly use the 'final' keyword for an enum
// // declaration
// logError("Illegal \"final\" modifier on enum ", name);
// } else {
// enums are implicitly final unless they contain a class body
// (which we won't know until we process the constants
Model.getCoreHelper().setLeaf(mClass, true);
// }
Model.getCoreHelper().setRoot(mClass, false);
// only do realizations on the 2nd pass.
if (getLevel() == 0) {
return;
}
if (interfaces != null) {
addInterfaces(mClass, interfaces, forceIt);
}
}
/**
* @param mClass
* @param interfaces
* @param forceIt
*/
private void addInterfaces(Object mClass, List<String> interfaces,
boolean forceIt) {
for (String interfaceName : interfaces) {
Object mInterface = null;
try {
mInterface =
getContext(interfaceName)
.getInterface(getClassifierName(interfaceName));
} catch (ClassifierNotFoundException e) {
if (forceIt && interfaceName != null && model != null) {
LOG.log(Level.INFO,
"Modeler: "
+ "forced creation of unknown interface "
+ interfaceName);
String packageName = getPackageName(interfaceName);
String classifierName =
getClassifierName(interfaceName);
Object mPackage =
(packageName.length() > 0)
? getPackage(packageName)
: model;
mInterface =
Model.getCoreFactory().buildInterface(
classifierName, mPackage);
newElements.add(mInterface);
} else {
warnClassifierNotFound(interfaceName,
"an abstraction");
}
}
// TODO: This should use the Model API's buildAbstraction - tfm
if (mInterface != null) {
Object mAbstraction =
getAbstraction(mInterface, mClass);
if (Model.getFacade().getSuppliers(mAbstraction).size()
== 0) {
Model.getCoreHelper().addSupplier(
mAbstraction,
mInterface);
Model.getCoreHelper().addClient(mAbstraction, mClass);
}
Model.getCoreHelper().setNamespace(
mAbstraction,
currentPackage);
Model.getCoreHelper().addStereotype(
mAbstraction,
getStereotype(CoreFactory.REALIZE_STEREOTYPE));
newElements.add(mAbstraction);
}
}
}
/**
* Called from the parser when an enumeration literal is found.
*
* @param name The name of the enumerationLiteral.
*/
void addEnumerationLiteral(String name) {
Object enumeration = parseState.getClassifier();
if (!isAEnumeration(enumeration)) {
throw new ParseStateException("not an Enumeration");
}
short mod = IDLParser.MOD_PUBLIC;
// | IDLParser.ACC_FINAL
// | IDLParser.ACC_STATIC;
addAttribute(mod, null, name, null, null, true);
// add an <<enum>> stereotype to distinguish it from fields
// in the class body?
}
/*
* Recognizer for enumeration. In our world an enumeration
* is a Class with the <<enumeration>> stereotype applied.
*/
private boolean isAEnumeration(Object element) {
if (!Model.getFacade().isAClass(element)) {
return false;
}
return Model.getExtensionMechanismsHelper().hasStereotype(element,
"enumeration");
}
/**
Common code used by addClass and addInterface.
@param newClassifier Supply one if none is found in the model.
@param name Name of the classifier.
@param modifiers String of modifiers.
@param javadoc The javadoc comment. null or "" if no comment available.
@param typeParameters List of types for parameters (not implemented)
@return The newly created/found classifier.
*/
private Object addClassifier(Object newClassifier,
String name,
short modifiers,
String javadoc,
List<String> typeParameters) {
Object mClassifier;
Object mNamespace;
if (parseState.getClassifier() != null) {
// the new classifier is a java inner class
mClassifier =
Model.getFacade().lookupIn(parseState.getClassifier(), name);
mNamespace = parseState.getClassifier();
} else {
// the new classifier is a top level java class
parseState.outerClassifier();
mClassifier = Model.getFacade().lookupIn(currentPackage, name);
mNamespace = currentPackage;
}
if (mClassifier == null) {
// if the classifier could not be found in the model
LOG.log(Level.INFO, "Created new classifier for {0}", name);
mClassifier = newClassifier;
Model.getCoreHelper().setName(mClassifier, name);
Model.getCoreHelper().setNamespace(mClassifier, mNamespace);
newElements.add(mClassifier);
} else {
// it was found and we delete any existing tagged values.
LOG.log(Level.INFO, "Found existing classifier for {0}", name);
// TODO: Rewrite existing elements instead? - tfm
cleanModelElement(mClassifier);
}
parseState.innerClassifier(mClassifier);
// set up the component residency (only for top level classes)
if (parseState.getClassifier() == null) {
// set the classifier to be a resident in its component:
// (before we push a new parse state on the stack)
// This test is carried over from a previous implementation,
// but I'm not sure why it would already be set - tfm
if (Model.getFacade().getElementResidences(mClassifier).isEmpty()) {
Object resident = Model.getCoreFactory()
.createElementResidence();
Model.getCoreHelper().setResident(resident, mClassifier);
Model.getCoreHelper().setContainer(resident,
parseState.getComponent());
}
}
// change the parse state to a classifier parse state
parseStateStack.push(parseState);
parseState = new ParseState(parseState, mClassifier, currentPackage);
setVisibility(mClassifier, modifiers);
// Add classifier documentation tags during first (or only) pass only
if (getLevel() <= 0) {
addDocumentationTag(mClassifier, javadoc);
}
return mClassifier;
}
/**
* Return the current import pass/level.
*
* @return 0, 1, or 2 depending on current import level and pass of
* processing. Returns -1 if level isn't defined.
*/
private int getLevel() {
Object level = this.getAttribute("level");
if (level != null) {
return ((Integer) level).intValue();
}
return -1;
}
/**
Called from the parser when a classifier is completely parsed.
*/
public void popClassifier() {
// Remove operations and attributes not in source
parseState.removeObsoleteFeatures();
// Remove inner classes not in source
parseState.removeObsoleteInnerClasses();
parseState = parseStateStack.pop();
}
/**
* Add an Operation to the current model
*
* @param modifiers
* A sequence of operation modifiers.
* @param returnType
* The return type of the operation.
* @param name
* The name of the operation as a string
* @param parameters
* A List of parameter declarations containing types and names.
* @param javadoc
* The javadoc comment. null or "" if no comment available.
* @return The operation.
*/
public Object addOperation (short modifiers,
String returnType,
String name,
List<ParameterDeclaration> parameters,
String javadoc) {
return addOperation(modifiers, EMPTY_STRING_LIST, returnType, name,
parameters, javadoc, false);
}
/**
* Called from the parser when an operation is found.
*
* @param modifiers
* A sequence of operation modifiers.
* @param returnType
* The return type of the operation.
* @param name
* The name of the operation as a string
* @param parameters
* A number of lists, each representing a parameter.
* @param javadoc
* The javadoc comment. null or "" if no comment available.
* @param forceIt
* Force addition by creating all that's missing.
* @return The operation.
*/
Object addOperation (short modifiers,
List<String> typeParameters,
String returnType,
String name,
List<ParameterDeclaration> parameters,
String javadoc,
boolean forceIt) {
if (typeParameters != null && typeParameters.size() > 0) {
logError("type parameters not supported on operation return type",
name);
}
Object mOperation = getOperation(name);
parseState.feature(mOperation);
// Model.getCoreHelper().setAbstract(mOperation,
// (modifiers & IDLParser.ACC_ABSTRACT) > 0);
// Model.getCoreHelper().setLeaf(mOperation,
// (modifiers & IDLParser.ACC_FINAL) > 0);
Model.getCoreHelper().setRoot(mOperation, false);
setOwnerScope(mOperation, modifiers);
setVisibility(mOperation, modifiers);
// if ((modifiers & IDLParser.ACC_SYNCHRONIZED) > 0) {
// Model.getCoreHelper().setConcurrency(mOperation,
// Model.getConcurrencyKind().getGuarded());
// } else
if (Model.getFacade().getConcurrency(mOperation)
== Model.getConcurrencyKind().getGuarded()) {
Model.getCoreHelper().setConcurrency(mOperation,
Model.getConcurrencyKind().getSequential());
}
Collection c = new ArrayList(Model.getFacade()
.getParameters(mOperation));
for (Object parameter : c) {
Model.getCoreHelper().removeParameter(mOperation, parameter);
}
Object mParameter;
String typeName;
Object mClassifier = null;
if (returnType == null
|| ("void".equals(returnType)
&& name.equals(Model.getFacade().getName(parseState
.getClassifier())))) {
// Constructor
Model.getCoreHelper().addStereotype(mOperation,
getStereotype(mOperation, "create", "BehavioralFeature"));
} else {
try {
mClassifier =
getContext(returnType).get(getClassifierName(returnType));
} catch (ClassifierNotFoundException e) {
if (forceIt && returnType != null && model != null) {
LOG.log(Level.INFO,
"Modeler.java: "
+ "forced creation of unknown classifier "
+ returnType);
String packageName = getPackageName(returnType);
String classifierName = getClassifierName(returnType);
Object mPackage =
(packageName.length() > 0) ? getPackage(packageName)
: model;
mClassifier = Model.getCoreFactory().buildClass(
classifierName, mPackage);
newElements.add(mClassifier);
} else {
warnClassifierNotFound(returnType,
"operation return type");
}
}
if (mClassifier != null) {
mParameter = buildReturnParameter(mOperation, mClassifier);
}
}
for (ParameterDeclaration parameter : parameters) {
typeName = parameter.getType();
// TODO: A type name with a trailing "..." represents
// a variable length parameter list. It can only be
// the last parameter and it gets converted to an array
// on method invocation, so perhaps we should model it that
// way (ie convert "Foo..." to "Foo[]"). - tfm - 20070329
if (typeName.endsWith("...")) {
logError("Unsupported variable length parameter list notation",
parameter.getName());
}
mClassifier = null;
try {
mClassifier =
getContext(typeName).get(getClassifierName(typeName));
} catch (ClassifierNotFoundException e) {
if (forceIt && typeName != null && model != null) {
LOG.log(Level.INFO,
"Modeler.java: "
+ "forced creation of unknown classifier "
+ typeName);
String packageName = getPackageName(typeName);
String classifierName = getClassifierName(typeName);
Object mPackage =
(packageName.length() > 0) ? getPackage(packageName)
: model;
mClassifier = Model.getCoreFactory().buildClass(
classifierName, mPackage);
newElements.add(mClassifier);
} else {
warnClassifierNotFound(typeName,
"operation params");
}
}
if (mClassifier != null) {
mParameter = buildInParameter(mOperation, mClassifier,
parameter.getName());
if (!Model.getFacade().isAClassifier(mClassifier)) {
// the type resolution failed to find a valid classifier.
logError("Modeler.java: a valid type for a parameter "
+ "could not be resolved:\n "
+ "In file: " + fileName + ", for operation: "
+ Model.getFacade().getName(mOperation)
+ ", for parameter: ",
Model.getFacade().getName(mParameter));
}
}
}
addDocumentationTag (mOperation, javadoc);
return mOperation;
}
private Object buildInParameter(Object operation, Object classifier,
String name) {
Object parameter = buildParameter(operation, classifier, name);
Model.getCoreHelper().setKind(
parameter, Model.getDirectionKind().getInParameter());
return parameter;
}
private Object buildReturnParameter(Object operation, Object classifier) {
Object parameter = buildParameter(operation, classifier, "return");
Model.getCoreHelper().setKind(
parameter, Model.getDirectionKind().getReturnParameter());
return parameter;
}
private Object buildParameter(Object operation, Object classifier,
String name) {
Object parameter =
Model.getCoreFactory().buildParameter(operation, classifier);
Model.getCoreHelper().setName(parameter, name);
return parameter;
}
/**
* Warn user that information available in input source will not be
* reflected accurately in the model.
*
* @param name
* name of the classifier which wasn't found
* @param operation -
* a string indicating what type of operation was being attempted
*/
private void warnClassifierNotFound(String name, String operation) {
logError("Modeler.java: a classifier (" + name
+ ") that was in the source "
+ "file could not be generated in the model ", operation);
}
/**
* Add an error message to the log to be shown to the user.
* <p>
* TODO: This currently just writes to the error log. It needs to return
* errors some place that the user can see them and deal with them. We
* also need a way to get the line and column numbers to help the user
* track the problem down.
*/
private void logError(String message, String identifier) {
LOG.log(Level.WARNING, message + " : " + identifier);
}
/**
* Called from the parser when an attribute is found.
*
* @param modifiers A sequence of attribute modifiers.
* @param typeSpec The attribute's type.
* @param name The name of the attribute.
* @param initializer The initial value of the attribute.
* @param javadoc The javadoc comment. null or "" if no comment available.
*/
public void addAttribute (short modifiers,
String typeSpec,
String name,
String initializer,
String javadoc) {
addAttribute(modifiers, typeSpec, name, initializer, javadoc, false);
}
/**
* Called from the parser when an attribute is found.
*
* @param modifiers A sequence of attribute modifiers.
* @param typeSpec The attribute's type.
* @param name The name of the attribute.
* @param initializer The initial value of the attribute.
* @param javadoc The javadoc comment. null or "" if no comment available.
* @param forceIt Force addition by creating all that's missing.
*/
void addAttribute (short modifiers,
String typeSpec,
String name,
String initializer,
String javadoc,
boolean forceIt) {
String multiplicity = "1_1";
Object mClassifier = null;
if (typeSpec != null) {
if (!arraysAsDatatype && typeSpec.indexOf('[') != -1) {
typeSpec = typeSpec.substring(0, typeSpec.indexOf('['));
multiplicity = "1_N";
}
// the attribute type
try {
// get the attribute type
mClassifier =
getContext(typeSpec).get(getClassifierName(typeSpec));
} catch (ClassifierNotFoundException e) {
if (forceIt && typeSpec != null && model != null) {
LOG.log(Level.INFO,
"Modeler.java: forced creation of"
+ " unknown classifier " + typeSpec);
String packageName = getPackageName(typeSpec);
String classifierName = getClassifierName(typeSpec);
Object mPackage =
(packageName.length() > 0) ? getPackage(packageName)
: model;
mClassifier =
Model.getCoreFactory().buildClass(
classifierName, mPackage);
newElements.add(mClassifier);
} else {
warnClassifierNotFound(typeSpec, "an attribute");
}
}
if (mClassifier == null) {
logError("failed to find or create type", typeSpec);
return;
}
}
// if we want to create a UML attribute:
if (mClassifier == null
|| noAssociations
|| Model.getFacade().isADataType(mClassifier)
) {
Object mAttribute = parseState.getAttribute(name);
if (mAttribute == null) {
mAttribute = buildAttribute(parseState.getClassifier(),
mClassifier, name);
}
parseState.feature(mAttribute);
setOwnerScope(mAttribute, modifiers);
setVisibility(mAttribute, modifiers);
Model.getCoreHelper().setMultiplicity(mAttribute, multiplicity);
if (Model.getFacade().isAClassifier(mClassifier)) {
// TODO: This should already have been done in buildAttribute
Model.getCoreHelper().setType(mAttribute, mClassifier);
} else {
// the type resolution failed to find a valid classifier.
logError("Modeler.java: a valid type for a parameter "
+ "could not be resolved:\n "
+ "In file: " + fileName + ", for attribute: ",
Model.getFacade().getName(mAttribute));
}
// Set the initial value for the attribute.
if (initializer != null) {
// we must remove line endings and tabs from the intializer
// strings, otherwise the classes will display horribly.
initializer = initializer.replace('\n', ' ');
initializer = initializer.replace('\t', ' ');
Object newInitialValue =
Model.getDataTypesFactory()
.createExpression("Java",
initializer);
Model.getCoreHelper().setInitialValue(
mAttribute,
newInitialValue);
}
// if ((modifiers & IDLParser.ACC_FINAL) > 0) {
// Model.getCoreHelper().setReadOnly(mAttribute, true);
// } else
if (Model.getFacade().isReadOnly(mAttribute)) {
Model.getCoreHelper().setReadOnly(mAttribute, true);
}
addDocumentationTag(mAttribute, javadoc);
}
// we want to create a UML association from the java attribute
else {
Object mAssociationEnd = getAssociationEnd(name, mClassifier);
// setTargetScope(mAssociationEnd, modifiers);
setVisibility(mAssociationEnd, modifiers);
Model.getCoreHelper().setMultiplicity(
mAssociationEnd,
multiplicity);
Model.getCoreHelper().setType(mAssociationEnd, mClassifier);
Model.getCoreHelper().setName(mAssociationEnd, name);
// if ((modifiers & IDLParser.ACC_FINAL) > 0) {
// Model.getCoreHelper().setReadOnly(mAssociationEnd, true);
// }
if (!mClassifier.equals(parseState.getClassifier())) {
// Because if they are equal,
// then getAssociationEnd(name, mClassifier) could return
// the wrong assoc end, on the other hand the navigability
// is already set correctly (at least in this case), so the
// next line is not necessary. (maybe never necessary?) - thn
Model.getCoreHelper().setNavigable(mAssociationEnd, true);
}
addDocumentationTag(mAssociationEnd, javadoc);
}
}
/**
Find a generalization in the model. If it does not exist, a
new generalization is created.
@param mPackage Look in this package.
@param parent The superclass.
@param child The subclass.
@return The generalization found or created.
*/
private Object getGeneralization(Object mPackage,
Object parent,
Object child) {
Object mGeneralization =
Model.getFacade().getGeneralization(child, parent);
if (mGeneralization == null) {
mGeneralization =
Model.getCoreFactory().buildGeneralization(
child, parent);
newElements.add(mGeneralization);
}
if (mGeneralization != null) {
Model.getCoreHelper().setNamespace(mGeneralization, mPackage);
}
return mGeneralization;
}
/**
* Find an abstraction<<realize>> in the model. If it does not
* exist, a new abstraction is created.
*
* @param parent The superclass.
* @param child The subclass.
* @return The abstraction found or created.
*/
private Object getAbstraction(Object parent,
Object child) {
Object mAbstraction = null;
for (Iterator i =
Model.getFacade().getClientDependencies(child).iterator();
i.hasNext();) {
mAbstraction = i.next();
Collection c = Model.getFacade().getSuppliers(mAbstraction);
if (c == null || c.size() == 0) {
Model.getCoreHelper()
.removeClientDependency(child, mAbstraction);
} else {
if (parent != c.toArray()[0]) {
mAbstraction = null;
} else {
break;
}
}
}
if (mAbstraction == null) {
mAbstraction = Model.getCoreFactory().buildAbstraction(
makeAbstractionName(child, parent),
parent,
child);
newElements.add(mAbstraction);
}
return mAbstraction;
}
/**
Find a package in the model. If it does not exist, a new
package is created.
@param name The name of the package.
@return The package found or created.
*/
private Object getPackage(String name) {
Object mPackage = searchPackageInModel(name);
if (mPackage == null) {
mPackage =
Model.getModelManagementFactory().buildPackage(
getRelativePackageName(name));
newElements.add(mPackage);
// TODO: This is redundant with addOwnedElement code below - tfm
Model.getCoreHelper().setNamespace(mPackage, model);
// Find the owner for this package.
if ("".equals(getPackageName(name))) {
Model.getCoreHelper().addOwnedElement(model, mPackage);
} else {
Model.getCoreHelper().addOwnedElement(
getPackage(getPackageName(name)),
mPackage);
}
}
return mPackage;
}
/**
* Search recursively for nested packages in the model. So if you
* pass a package org.argouml.kernel , this method searches for a package
* kernel, that is owned by a package argouml, which is owned by a
* package org. This method is required to nest the parsed packages.
*
* @param name The fully qualified package name of the package we
* are searching for.
* @return The found package or null, if it is not in the model.
*/
private Object searchPackageInModel(String name) {
if ("".equals(getPackageName(name))) {
return Model.getFacade().lookupIn(model, name);
}
Object owner = searchPackageInModel(getPackageName(name));
return owner == null
? null
: Model.getFacade().lookupIn(owner, getRelativePackageName(name));
}
/**
Find an operation in the currentClassifier. If the operation is
not found, a new is created.
@param name The name of the operation.
@return The operation found or created.
*/
private Object getOperation(String name) {
Object mOperation = parseState.getOperation(name);
if (mOperation != null) {
LOG.info("Getting the existing operation " + name);
} else {
LOG.info("Creating a new operation " + name);
Object cls = parseState.getClassifier();
Object returnType = ProjectManager.getManager()
.getCurrentProject().getDefaultReturnType();
mOperation = Model.getCoreFactory().buildOperation2(cls, returnType,
name);
newElements.add(mOperation);
}
return mOperation;
}
/**
* Build a new attribute in the current classifier.
*
* @param classifier
* the model were are reverse engineering into
* @param type
* the the type of the new attribute
* @param name
* The name of the attribute.
* @return The attribute found or created.
*/
private Object buildAttribute(Object classifier, Object type, String name) {
Object mAttribute =
Model.getCoreFactory().buildAttribute2(classifier, type);
newElements.add(mAttribute);
Model.getCoreHelper().setName(mAttribute, name);
return mAttribute;
}
/**
* Find an associationEnd for a binary Association from the
* currentClassifier to the type specified.
* If not found, a new is created.
*
* @param name
* The name of the attribute.
* @param mClassifier
* Where the association ends.
* @return The attribute found or created.
*/
private Object getAssociationEnd(String name, Object mClassifier) {
Object mAssociationEnd = null;
for (Iterator i = Model.getFacade().getAssociationEnds(mClassifier)
.iterator(); i.hasNext();) {
Object ae = i.next();
Object assoc = Model.getFacade().getAssociation(ae);
if (name.equals(Model.getFacade().getName(ae))
&& Model.getFacade().getConnections(assoc).size() == 2
&& Model.getFacade().getType(
Model.getFacade().getNextEnd(ae))
== parseState.getClassifier()) {
mAssociationEnd = ae;
}
}
if (mAssociationEnd == null && !noAssociations) {
String newName =
makeAssociationName(parseState.getClassifier(),
mClassifier);
Object mAssociation = buildDirectedAssociation(
newName, parseState.getClassifier(), mClassifier);
// this causes a problem when mClassifier is not only
// at one assoc end: (which one is the right one?)
mAssociationEnd =
Model.getFacade().getAssociationEnd(
mClassifier,
mAssociation);
}
return mAssociationEnd;
}
/**
* Build a unidirectional association between two Classifiers.
*
* @param name name of the association
* @param sourceClassifier source classifier (end which is non-navigable)
* @param destClassifier destination classifier (end which is navigable)
* @return newly created Association
*/
public static Object buildDirectedAssociation(
String name,
Object sourceClassifier,
Object destClassifier) {
return Model.getCoreFactory().buildAssociation(
destClassifier, true, sourceClassifier, false,
name);
}
/**
Get the stereotype with a specific name.
@param name The name of the stereotype.
@return The stereotype.
*/
private Object getStereotype(String name) {
LOG.fine("Trying to find a stereotype of name <<" + name + ">>");
// Is this line really safe wouldn't it just return the first
// model element of the same name whether or not it is a stereotype
Object stereotype = Model.getFacade().lookupIn(model, name);
if (stereotype == null) {
LOG.fine("Couldn't find so creating it");
return
Model.getExtensionMechanismsFactory()
.buildStereotype(name, model);
}
if (!Model.getFacade().isAStereotype(stereotype)) {
// and so this piece of code may create an existing stereotype
// in error.
LOG.fine("Found something that isn't a stereotype so creating it");
return
Model.getExtensionMechanismsFactory()
.buildStereotype(name, model);
}
LOG.fine("Found it");
return stereotype;
}
/**
* Find the first suitable stereotype with baseclass for a given object.
*
* @param me
* @param name
* @param baseClass
* @return the stereotype if found
*
* @throws IllegalArgumentException if the desired stereotypes for
* the modelelement and baseclass was not found and could not be created.
* No stereotype is created.
*/
private Object getStereotype(Object me, String name, String baseClass) {
Collection models =
ProjectManager.getManager().getCurrentProject().getModels();
Collection stereos =
Model.getExtensionMechanismsHelper().getAllPossibleStereotypes(
models, me);
Object stereotype = null;
if (stereos != null && stereos.size() > 0) {
Iterator iter = stereos.iterator();
while (iter.hasNext()) {
stereotype = iter.next();
if (Model.getExtensionMechanismsHelper()
.isStereotypeInh(stereotype, name, baseClass)) {
LOG.info("Returning the existing stereotype of <<"
+ Model.getFacade().getName(stereotype) + ">>");
return stereotype;
}
}
}
// Instead of failing, this should create any stereotypes that it
// requires. Most likely cause of failure is that the stereotype isn't
// included in the profile that is being used. - tfm 20060224
stereotype = getStereotype(name);
if (stereotype != null) {
Model.getExtensionMechanismsHelper().addBaseClass(stereotype, me);
return stereotype;
}
// This should never happen then:
throw new IllegalArgumentException("Could not find "
+ "a suitable stereotype for " + Model.getFacade().getName(me)
+ " - stereotype: <<" + name
+ ">> base: " + baseClass);
}
/**
* This classifier was earlier generated by reference but now it is
* its time to be parsed so we clean out remnants.
*
* @param element that they are removed from
*/
private void cleanModelElement(Object element) {
Object tv =
Model.getFacade().getTaggedValue(element, Facade.GENERATED_TAG);
while (tv != null) {
Model.getUmlFactory().delete(tv);
tv =
Model.getFacade().getTaggedValue(
element, Facade.GENERATED_TAG);
}
}
/**
Get the package name from a fully specified classifier name.
@param name A fully specified classifier name.
@return The package name.
*/
private String getPackageName(String name) {
int lastDot = name.lastIndexOf('.');
if (lastDot == -1) {
return "";
}
String pkgName = name.substring(0, lastDot);
return pkgName;
// TODO: Fix handling of inner classes along the lines of the
// following...
// If the last element begins with an uppercase character, assume
// that we've really got a class, not a package. A better strategy
// would be to defer until we can disambiguate, but this should be
// better than what we have now for the more common case of inner
// classes.
// if (Character.isUpperCase(
// getRelativePackageName(pkgName).charAt(0))) {
// return getPackageName(pkgName);
// } else {
// return pkgName;
// }
}
/**
* Get the relative package name from a fully qualified
* package name. So if the parameter is 'org.argouml.kernel'
* the method is supposed to return 'kernel' (the package
* kernel is in package 'org.argouml').
*
* @param packageName A fully qualified package name.
* @return The relative package name.
*/
private String getRelativePackageName(String packageName) {
// Since the relative package name corresponds
// to the classifier name of a fully qualified
// classifier, we simply use this method.
return getClassifierName(packageName);
}
/**
Get the classifier name from a fully specified classifier name.
@param name A fully specified classifier name.
@return The classifier name.
*/
private String getClassifierName(String name) {
int lastDot = name.lastIndexOf('.');
if (lastDot == -1) {
return name;
}
return name.substring(lastDot + 1);
}
/**
Set the visibility for a model element.
@param element The model element.
@param modifiers A sequence of modifiers which may contain
'private', 'protected' or 'public'.
*/
private void setVisibility(Object element,
short modifiers) {
// if ((modifiers & IDLParser.ACC_PRIVATE) > 0) {
// Model.getCoreHelper().setVisibility(
// element,
// Model.getVisibilityKind().getPrivate());
// } else if ((modifiers & IDLParser.ACC_PROTECTED) > 0) {
// Model.getCoreHelper().setVisibility(
// element,
// Model.getVisibilityKind().getProtected());
// } else
if ((modifiers & IDLParser.MOD_PUBLIC) > 0) {
Model.getCoreHelper().setVisibility(
element,
Model.getVisibilityKind().getPublic());
} else {
// Default Java visibility is "package"
Model.getCoreHelper().setVisibility(
element,
Model.getVisibilityKind().getPackage());
}
}
/**
Set the owner scope for a feature.
@param feature The feature.
@param modifiers A sequence of modifiers which may contain
'static'.
*/
private void setOwnerScope(Object feature, short modifiers) {
// Model.getCoreHelper().setStatic(
// feature, (modifiers & IDLParser.ACC_STATIC) > 0);
}
/**
Get the context for a classifier name that may or may not be
fully qualified.
@param name The classifier name.
*/
private Context getContext(String name) {
Context context = parseState.getContext();
String packageName = getPackageName(name);
if (!"".equals(packageName)) {
context = new PackageContext(context, getPackage(packageName));
}
return context;
}
/**
* Add the javadocs as a tagged value 'documentation' to the model
* element. All comment delimiters are removed prior to adding the
* comment.
*
* Added 2001-10-05 STEFFEN ZSCHALER.
*
* @param modelElement the model element to which to add the documentation
* @param sJavaDocs the documentation comment to add ("" or null
* if no java docs)
*/
private void addDocumentationTag(Object modelElement, String sJavaDocs) {
if ((sJavaDocs != null)
&& (sJavaDocs.trim().length() >= 5)) {
StringBuffer sbPureDocs = new StringBuffer(80);
String sCurrentTagData = null;
int nStartPos = 3; // skip the leading /**
boolean fHadAsterisk = true;
while (nStartPos < sJavaDocs.length()) {
switch (sJavaDocs.charAt (nStartPos)) {
case '*':
fHadAsterisk = true;
nStartPos++;
break;
case ' ': // all white space, hope I didn't miss any ;-)
case '\t':
// ignore white space before the first asterisk
if (!fHadAsterisk) {
nStartPos++;
break;
}
default:
// normal comment text or standard tag
// check ahead for tag
int j = nStartPos;
while ((j < sJavaDocs.length())
&& ((sJavaDocs.charAt (j) == ' ')
|| (sJavaDocs.charAt (j) == '\t'))) {
j++;
}
if (j < sJavaDocs.length()) {
int nTemp = sJavaDocs.indexOf ('\n', nStartPos);
if (nTemp == -1) {
nTemp = sJavaDocs.length();
} else {
nTemp++;
}
sbPureDocs.append(sJavaDocs.substring(nStartPos,
nTemp));
nStartPos = nTemp;
}
fHadAsterisk = false;
}
}
sJavaDocs = sbPureDocs.toString();
/*
* After this, we have the documentation text, but there's still a
* trailing '/' left, either at the end of the actual comment text
* or at the end of the last tag.
*/
sJavaDocs = removeTrailingSlash(sJavaDocs);
// Now store documentation text in a tagged value
Model.getExtensionMechanismsHelper().addTaggedValue(
modelElement,
Model.getExtensionMechanismsFactory().buildTaggedValue(
Argo.DOCUMENTATION_TAG, sJavaDocs));
}
}
/*
* Remove a trailing slash, including the entire line if it's the only thing
* on the line.
*/
private String removeTrailingSlash(String s) {
if (s.endsWith("\n/")) {
return s.substring(0, s.length() - 2);
} else if (s.endsWith("/")) {
return s.substring(0, s.length() - 1);
} else {
return s;
}
}
/**
* Manage collection of parsed method calls. Used for reverse engineering of
* interactions.
*/
/**
* Add a parsed method call to the collection of method calls.
* @param methodName
* The method name called.
*/
public void addCall(String methodName) {
methodCalls.add(methodName);
}
/**
* Get collection of method calls.
* @return list containing collected method calls
*/
public synchronized List<String> getMethodCalls() {
return methodCalls;
}
/**
* Clear collected method calls.
*/
public void clearMethodCalls() {
methodCalls.clear();
}
/**
* Add a local variable declaration to the list of variables.
*
* @param type type of declared variable
* @param name name of declared variable
*/
public void addLocalVariableDeclaration(String type, String name) {
localVariables.put(name, type);
}
/**
* Return the collected set of local variable declarations.
*
* @return hashtable containing all local variable declarations.
*/
public Hashtable getLocalVariableDeclarations() {
return localVariables;
}
/**
* Clear the set of local variable declarations.
*/
public void clearLocalVariableDeclarations() {
localVariables.clear();
}
/**
* Get the elements which were created while reverse engineering this file.
*
* @return the collection of elements
*/
public Collection getNewElements() {
return newElements;
}
/**
* Set flag that controls name generation. Artificial names are generated
* by default for historical reasons, but in most cases they are just
* clutter.
*
* @param generateNamesFlag true to generate artificial names of the form
* "From->To" for Associations, Dependencies, etc.
*/
public void setGenerateNames(boolean generateNamesFlag) {
generateNames = generateNamesFlag;
}
}