Skip to content
Permalink
Branch: master
Find file Copy path
Find file Copy path
Fetching contributors…
Cannot retrieve contributors at this time
executable file 5503 lines (4815 sloc) 191 KB
/*
Copyright: All contributers to the Umple Project
This file is made available subject to the open source license found at:
http://umple.org/license
This file analyze class related umple meta model tokens such class declarations,
associations and attributes and populates the umple meta model.
Please refer to UmpleInternalParser.ump for more details.
*/
namespace cruise.umple.compiler;
class UmpleInternalParser
{
depend java.util.regex.*;
depend cruise.umple.parser.Token;
internal AssociationVariable[] unlinkedAssociationVariables;
internal Association[] unlinkedAssociations;
internal Map<Position,String> positionToClassNameReference = new HashMap<Position, String>();
internal Map<UmpleClassifier,List<Token>> unanalyzedInvariants = new HashMap<UmpleClassifier,List<Token>>();
internal Map<UmpleClassifier,List<String>> unlinkedExtends = new HashMap<UmpleClassifier,List<String>>();
internal Map<UmpleClassifier,List<Token>> unlinkedExtendsTokens = new HashMap<UmpleClassifier,List<Token>>();
internal Map<UmpleClassifier,List<Token>> unlinkedExtendsTokensInterface = new HashMap <UmpleClassifier, List<Token>>();
internal Map<UmpleClassifier,List<String>> unlinkedInterfaceExtends = new HashMap<UmpleClassifier,List<String>>();
internal Map<UmpleClass,List<Token>> unlinkedKeysTokens = new HashMap<UmpleClass,List<Token>>();
internal Map<UmpleClassifier,List<Token>> unanalyzedInjectionTokens = new HashMap<UmpleClassifier,List<Token>>();
// List of comments populated by the parser to be added to the next class, attribute, association, method, etc.
internal Comment[] lastComments;
// last attribute made.
internal Attribute lastattr = null;
// last association made.
internal AssociationVariable lastassoc = null;
Position lastassocPosition = null;
internal Map<Token, UmpleClass> attributeAutouniqueImmutable = null;
/*
* Analyze class definitions and related tokens.
*
* @param t The current token that will be analyzed to determine how to further make use of it (is it a comment, class, etc?)
* @param analysisStep Used to determine whether or not things should be analyzed more than once (multi-pass).
*/
private void analyzeClassToken(Token t, int analysisStep)
{
if (analysisStep != 2)
{
shouldProcessAgain = shouldProcessAgain || (analysisStep == 1);
return;
}
// Only need to clear comments if there actually was comments.
boolean shouldConsumeComment = lastComments.size() > 0;
// Determine what the current token is primarily, and based on that the analysis procedure is determined.
if (t.isStatic("//") || t.isStatic("/*") || t.isStatic("*/"))
{
shouldConsumeComment = false;
}
else if (t.is("useStatement") )
{
analyzeAllTokens(t);
}
else if (t.is("strictness") )
{
// unimplemented feature. Issue a warning that it is currently not fully implemented
setFailedPosition(t.getPosition(), 9999, t.getName(), t.toString());
}
else if (t.is("strictnessMessage"))
{
if(t.getValue("message").equals("allow"))
{
allowMessage(Integer.parseInt(t.getValue("messageNumber")));
}
else if(t.getValue("message").equals("disallow"))
{
disallowMessage(Integer.parseInt(t.getValue("messageNumber")));
}
else if(t.getValue("message").equals("ignore"))
{
ignoreMessage(Integer.parseInt(t.getValue("messageNumber")));
}
else if(t.getValue("message").equals("expect"))
{
expectMessage(Integer.parseInt(t.getValue("messageNumber")));
}
}
else if (t.is("namespace"))
{
currentPackageName = t.getValue();
if (model.getDefaultNamespace() == null)
{
model.setDefaultNamespace(currentPackageName);
}
}
else if (t.is("inlineComment"))
{
analyzeComment(t, false);
shouldConsumeComment = addToLastAttributeOrAssociation(t);
}
else if (t.is("annotationComment"))
{
analyzeComment(t, true);
shouldConsumeComment = addToLastAttributeOrAssociation(t);
}
else if (t.is("multilineComment"))
{
analyzeMultilineComment(t);
shouldConsumeComment = false;
}
else if (t.is("classDefinition"))
{
analyzeClass(t);
}
else if (t.is("externalDefinition"))
{
if (t.getValue("interface")!=null)
analyzeExternalInterface(t);
else
analyzeExternal(t);
}
else if (t.is("interfaceDefinition"))
{
analyzeInterface(t);
}
else if (t.is("associationClassDefinition"))
{
analyzeAssociationClass(t);
}
else if (t.is("associationDefinition"))
{
analyzeAllAssociations(t);
}
else if (t.is("toplevelException"))
{
analyzeToplevelException(t);
}
// This essentially "clears" the comments in the list so that new comments, when parsed, will be the ones appearing above
// classes, methods, attributes, etc (whichever comes next) rather than old comments propogating everywhere.
if (shouldConsumeComment)
{
lastComments.clear();
}
}
/*
* If the last attribute/association made matches the line number of the comment, add the comment to the attribute/association.
*
* @param t the comment token that is used to see the position of the comment
*/
private boolean addToLastAttributeOrAssociation(Token t)
{
if (t.getPosition() == null) {return false;}
if (lastattr != null)
{
if (lastattr.getPosition().getLineNum() == t.getPosition().getLineNum())
{
// this will only add the current comment, as there can only be 1 inline comment per line
for (Comment c : lastComments)
{
lastattr.addComment(c);
}
return true;
}
else
{
lastattr = null;
}
}
else if (lastassoc != null && lastassocPosition != null)
{
if (lastassocPosition.getLineNum() == t.getPosition().getLineNum())
{
// this will only add the current comment, as there can only be 1 inline comment per line
for (Comment c : lastComments)
{
lastassoc.addComment(c);
}
return true;
}
else
{
lastassoc = null;
}
}
return false;
}
/*
* Analyze class content tokens.
*
* @param token The current token that will be analyzed to determine how to further make use of it (is it a method, comment,
* attribute, etc?)
* @param aClass The Umple class used such that parsed content such as methods, attributes, comments, etc may be added to
* it.
* @param analysisStep Used to determine whether or not things should be analyzed more than once (multi-pass).
*/
private void analyzeClassToken(Token token, UmpleClass aClass, int analysisStep)
{
if (analysisStep != 1)
{
return;
}
// Only need to clear comments if there actually was comments.
boolean shouldConsumeComment = lastComments.size() > 0;
// Determine what the current token is primarily, and based on that the analysis procedure is determined.
if (token.isStatic("//") || token.isStatic("/*") || token.isStatic("*/"))
{
shouldConsumeComment = false;
}
else if (token.is("inlineComment"))
{
analyzeComment(token, false);
shouldConsumeComment = addToLastAttributeOrAssociation(token);
}
else if (token.is("annotationComment"))
{
analyzeComment(token, true);
shouldConsumeComment = addToLastAttributeOrAssociation(token);
}
else if (token.is("multilineComment"))
{
analyzeMultilineComment(token);
shouldConsumeComment = false;
}
// TODO Under development
else if (token.is("classDefinition"))
{
UmpleClass childClass = analyzeClass(token);
Token p = token.getParentToken();
for (Token t: p.getSubTokens()) {
if (t.getValue() == aClass.getName()) {
if (aClass.getName() == childClass.getName()) {
setFailedPosition(token.getPosition(),11,"Class",childClass.getName());
}
if (unlinkedExtends.get(childClass) == null) {
List<String> extend = new ArrayList<>();
extend.add(aClass.getName());
unlinkedExtends.put(childClass, extend);
} else {
setFailedPosition(token.getPosition(),34,childClass.getName(),aClass.getName());
break;
}
if (unlinkedExtendsTokens.get(childClass) == null) {
List<Token> extendToken = new ArrayList<>();
extendToken.add(t);
unlinkedExtendsTokens.put(childClass, extendToken);
} else {
setFailedPosition(token.getPosition(),34,childClass.getName(),aClass.getName());
break;
}
}
}
}
else if (token.is("constantDeclaration"))
{
analyzeConstant(token,aClass);
}
else if(token.is("constantDeclarationDeprecated"))
{
setFailedPosition(token.getPosition(), 901);
analyzeConstant(token,aClass);
}
else if (token.is("attribute"))
{
analyzeAttribute(token,aClass);
}
else if (token.is("testCase"))
{
analyzeClassTestCase(token,aClass);
}
else if (token.getValue("mixsetDefinition") != null) {
analyzeMixsetBodyToken(token);
}
else if (token.is("extraCode"))
{
analyzeExtraCode(token,aClass);
}
else if (token.is("concreteMethodDeclaration"))
{
analyzeMethod(token,aClass);
}
else if (token.is("depend"))
{
Depend d = new Depend(token.getValue());
aClass.addDepend(d);
}
else if (token.is("inlineAssociation"))
{
analyzeinlineAssociation(token,aClass);
}
else if (token.is("symmetricReflexiveAssociation"))
{
analyzeSymmetricReflexiveAssociation(token,aClass);
}
else if (token.is("invariant"))
{
if(!unanalyzedInvariants.containsKey(aClass))
{
unanalyzedInvariants.put(aClass, new ArrayList<Token>());
}
unanalyzedInvariants.get(aClass).add(token);
}
else if (token.is("enumerationDefinition"))
{
// Issue 1008
analyzeEnumeration(token, aClass);
}
else if (token.is("exception"))
{
analyzeException(token,aClass);
}
// This essentially "clears" the comments in the list so that new comments, when parsed, will be the ones appearing above
// classes, methods, attributes, etc (whichever comes next) rather than old comments propogating everywhere.
if (shouldConsumeComment)
{
lastComments.clear();
}
}
private void analyzeClassTestCase(Token token, UmpleClass aClass) {
UmpleTestCase uTestCase = new UmpleTestCase ("", aClass);
UmpleAssertion assertion = new UmpleAssertion("","","","","",uTestCase);
uTestCase.setName(token.getValue("testCaseName"));
for (Token subToken : token.getSubTokens())
{
if (subToken.getName().equals("assertion"))
{
assertion.setAssertCode(token.getValue("code"));
//assertion.setType("true");
assertion.setLevel("method");
uTestCase.addUmpleAssertion(assertion);
}
}
}
/**
* Analyzes a comment to determine if it should be added into the list of currently parsed comments waiting to be added to
* a class, attribute, association, method or otherwise.
*
* Note that this is for an inline comment rather than a multiline comment.
*
* @param token The current token which has been flagged to be a comment to analyze, containing its value.
*/
private void analyzeComment(Token token, boolean isAnnotation)
{
String theValue = "";
// Special comment directive to force umpleoutput directives to be added
// In every class
if (token.getValue().startsWith("@outputumplesource")) {
outputUmpleSource = true;
return;
}
if (!token.getValue().equals("$?[End_of_model]$?"))
{
theValue = token.getValue();
Comment theComment = new Comment(theValue);
if(isAnnotation) {
theComment.setAnnotation(true);
}
lastComments.add(theComment);
}
}
/**
* Analyzes a comment to determine if it should be added into the list of currently parsed comments waiting to be added to
* a class, attribute, association, method or otherwise.
*
* Note that this is for a multiline comment, which essentially means the possibility of multiple inline comments (1 per line)
* that will be concatenated together.
*
* @param token The current token which has been flagged to be a comment to analyze, containing its value.
*/
private void analyzeMultilineComment(Token token)
{
String inlineComments[] = token.getValue().split("\n");
// Go through the inline comments and add them to the list of comments waiting to be applied
String theComment = "";
for (int i = 0; i < inlineComments.length; i++)
{
theComment = inlineComments[i];
if(theComment.startsWith("@outputumplesource")) {
outputUmpleSource = true;
}
else {
Comment comment = new Comment(theComment);
comment.setIsInline(false);
lastComments.add(comment);
}
}
}
// Link associations, association variables and extends that were "defined" after their use
private void postTokenClassAnalysis()
{
if (verifyClassesInUse())
{
checkSingletonAssociations();
addUnlinkedAssociationVariables();
addUnlinkedAssociations();
addUnlinkedExtends();
addAutogeneratedMethodsForAssociations();
//required for parsing specializedAssociations
checkExtendsForCycles();
createSpecializedLinks();
checkDuplicateAssociationNames();
checkDuplicateAssociationNamesClassHierarchy();
checkIgnoredAssociations();
checkSubclassSameAssociationDifferentRoleNames();
checkExtendsForCycles();
checkSortedAssociations();
checkAssociationsValidity();
//Issue 1084
addUnlinkedKeys();
addAutogeneratedMethodsForStateMachines();
//Solution to issue 543
//checkExtendsClass();
checkAbstractClass();
analyzeInvariants();
//Solution to issue 502
checkAttributeConflictImmutableAutounique();
//Issue 610
checkAssociationClassKeys();
//issue 841
checkSingletonParent();
// this method sets the isDistributed flag for classes.
analyzeDistributedClasses();
setParentClassesDefaultInterface();
checkAssociationsForDistributed();
//checkDistributedAssociations();
}
}
private void checkAssociationsForDistributed()
{
if(model.getDistributed()){
for (Association association : model.getAssociations())
{
if (associationIsBetweenClassAndInterface (association)){continue;}
if (associationIsBetweenClassAndTrait(association)){continue;}
AssociationEnd myEnd = association.getEnd(0);
AssociationEnd yourEnd = association.getEnd(1);
if(getModel().getUmpleTraitTypeParameter(myEnd.getClassName()) || getModel().getUmpleTrait(myEnd.getClassName())!=null) return ;
if(getModel().getUmpleTraitTypeParameter(yourEnd.getClassName()) || getModel().getUmpleTrait(yourEnd.getClassName())!=null) return ;
UmpleClass myClass = model.getUmpleClass(myEnd.getClassName());
UmpleClass yourClass = model.getUmpleClass(yourEnd.getClassName());
if(myClass.getIsDistributed()&& !yourClass.getIsDistributed()&&association.getIsRightNavigable())
{
getParseResult().addErrorMessage(new ErrorMessage(7002,association.getTokenPosition(),myClass.getName(),yourClass.getName()));
}
}
}
}
private void checkDistributedAssociations()
{
for (UmpleClass uClass: model.getUmpleClasses())
{
if(uClass.getIsDistributed())
{
}
}
}
/*
* Parent of each class that has default interface must have default interface.
*/
private void setParentClassesDefaultInterface()
{
for (UmpleClass uClass: model.getUmpleClasses())
{
if ((uClass.getNeedsDefaultInterface())&&((uClass.getIsDistributed())||(uClass.getHasProxyPattern())))
{
if(uClass.getExtendsClass()!=null)
{
if(!uClass.getExtendsClass().getNeedsDefaultInterface()&&!uClass.getExtendsClass().getIsDistributed())
{
setParentDefaultInterfaceRecursive(uClass.getExtendsClass(),uClass.getIsDistributed());
}
}
}
}
}
private void setParentDefaultInterfaceRecursive(UmpleClass uClass,Boolean isDistributed)
{
//if(!(uClass.getIsAbstract())&&!uClass.isImmutable()&&!uClass.getIsSingleton())
if(true)
{
uClass.setIsDefaultInterfaceRemoteRMI(isDistributed);
uClass.setNeedsDefaultInterface(true);
if(uClass.getExtendsClass()!=null)
{
if(!uClass.getExtendsClass().getNeedsDefaultInterface())
{
setParentDefaultInterfaceRecursive(uClass.getExtendsClass(),isDistributed);
}
}
}
}
/*
* Checking each class for distributabilty and chekning parent classes and interfaces as well as the system.
*/
private void analyzeDistributedClasses()
{
if(model.getDistributeOn())
{
for (UmpleClass uClass: model.getUmpleClasses())
{
if (model.getDistributeForced()&&!(uClass.getIsAbstract())&&!uClass.isImmutable())
{ model.setDistributed(true);
uClass.setDistributeTechnology(model.getDistributeTechnology());
uClass.setIsDistributed(true);
//uClass.setHasProxyPattern(true);
uClass.setNeedsDefaultInterface(true);
}
if (!uClass.getIsDistributed())
{ if(checkIsDistributed(uClass))
{
uClass.setNeedsDefaultInterface(true);
}
}
for(UmpleInterface parentInterface :uClass.getParentInterface())
{
if (parentInterface.getIsDistributable())
{
uClass.setNeedsDefaultInterface(false);
}
}
}
}
}
private Boolean checkIsDistributed(UmpleClass uClass)
{
/******
if the class implements d distributable interface it is also distributed. Here it has to be checked whether the class has more than one immediate distributable interfaces
First check the parent interfaces, then check the class itself, if none are distributable, check for parent classes
******/
int distributedParents=0;
for (UmpleInterface parentInterface: uClass.getParentInterface())
{
if(checkIsDistributed(parentInterface)&&!(uClass.getIsAbstract())&&!uClass.isImmutable())
{
distributedParents+=1;
uClass.setDistributeTechnology(parentInterface.getDistributeTechnology());
uClass.setIsDistributed(true);
model.setDistributed(true);
//uClass.setHasProxyPattern(true);
uClass.setNeedsDefaultInterface(false);
if (parentInterface.getIsDistributable())
{
uClass.setNeedsDefaultInterface(false);
}
}
}
if(distributedParents==1)
{
return true;
}
/*
First it is checked if the class is distributable itself. Then the parent classes
*/
if(uClass.getIsDistributable()&&!(uClass.getIsAbstract())&&!uClass.isImmutable())
{
//uClass.setHasProxyPattern(true);
uClass.setNeedsDefaultInterface(true);
uClass.setIsDistributed(true);
model.setDistributed(true);
return true;
}
if(uClass.getExtendsClass()!=null)
{
if(checkIsDistributed(uClass.getExtendsClass())&&!(uClass.getIsAbstract()))
{
uClass.setIsDistributed(true);
model.setDistributed(true);
//uClass.setHasProxyPattern(true);
uClass.setNeedsDefaultInterface(true);
uClass.setDistributeTechnology(uClass.getExtendsClass().getDistributeTechnology());
return true;
}
}
return false;
}
// this method checks for all of the interfaces recursively and returns true if the interface extends a distributable interface
private Boolean checkIsDistributed(UmpleInterface uInterface)
{
if(uInterface.getIsDistributable())
{
return true;
}
/*for (UmpleInterface parentInterface : uInterface.getExtendsInterface())
{
if(checkIsDistributed(parentInterface))
{
uInterface.setDistributeTechnology(parentInterface.getDistributeTechnology());
return true;
}
}*/
return false;
}
/*
* Analyze subtoken distributable to check which keyword is parsed
*/
private void analyzeDistributableInterface(Token distributableToken, UmpleInterface aInterface){
// If the "DistributableRMI" keyword is parsed, make the Umple interface Distributable.
if (distributableToken.getValue("distributable")!= null)
{
aInterface.setIsDistributable(true);
if(distributableToken.getValue("distributeTech")!=null)
aInterface.setDistributeTechnology(distributableToken.getValue("distributeTech"));
}
}
//Issue 841
private void checkSingletonParent()
{
for (UmpleClass uClass: model.getUmpleClasses())
{
if(uClass.getExtendsClass()!=null) {
if(uClass.getExtendsClass().getIsSingleton()) {
getParseResult().addErrorMessage(new ErrorMessage(40,uClass.getPosition(0),uClass.getName(),uClass.getExtendsClass().getName()));
}
}
}
}
//Issue 686
private void checkCodeInjections()
{
for(Map.Entry<UmpleClassifier, List<Token>> entry : unanalyzedInjectionTokens.entrySet())
{
for(Token injectToken : entry.getValue())
{
String operationSource = getOperationSource(injectToken);
if ("".equals(operationSource))
operationSource = "generated";
checkCodeInjectionValidity(injectToken, (UmpleClass) entry.getKey(), operationSource);
}
}
}
public void analyzeInvariants()
{
for(UmpleClassifier uClass:unanalyzedInvariants.keySet())
{
for(Token invariant:unanalyzedInvariants.get(uClass))
{
analyzeInvariant(invariant,uClass);
}
}
}
//Issue 610
private void checkAssociationClassKeys()
{
for (UmpleClass uClass: model.getUmpleClasses())
{
if(uClass instanceof AssociationClass) {
AssociationClass assocClass = (AssociationClass)uClass;
assocClass.addMissingKeyIfNeeded();
}
}
}
//Issue 492
private void checkNonReachableStates()
{
List<State> lStates = new ArrayList<State>();
List<State> lParentStates = new ArrayList<State>();
for (UmpleClass uClass: model.getUmpleClasses())
{
for (StateMachine sm: uClass.getStateMachines())
{
//Enum check
if(((sm.getAllEvents().size() > 0) && (sm.getAllTransitions().size() > 0)) || (sm.getNestedStateMachines().size() >0))
{
lStates = new ArrayList<State>();
State state = sm.getStartState();
checkReachableStates(state, lStates, lParentStates);
for(State sp : lParentStates)
{
if(!lStates.contains(sp))
{
lStates.add(sp);
}
}
raiseWarningUnreachable(sm, lStates);
}
}
}
}
//Issue 492
private void checkReachableStates(State s, List<State> ls, List<State> lp)
{
if(s == null)
return;
if(!ls.contains(s))
ls.add(s);
for ( Transition transition: s.getTransitions())
{
if(!ls.contains(transition.getNextState()))
checkReachableStates(transition.getNextState(), ls, lp);
}
if(s.hasNestedStateMachines())
{
for(StateMachine nestedSm: s.getNestedStateMachines())
{
checkReachableStates(nestedSm.getStartState(), ls, lp);
}
}
StateMachine aux = s.getStateMachine();
State fatherState = null;
while(aux.getParentState() != null)
{
fatherState = aux.getParentState();
if(!lp.contains(fatherState))
{
lp.add(fatherState);
for ( Transition transition: fatherState.getTransitions())
{
if(!ls.contains(transition.getNextState()))
checkReachableStates(transition.getNextState(), ls, lp);
}
}
if (fatherState.getStateMachine() == null)
return;
aux = fatherState.getStateMachine();
}
}
private void raiseWarningUnreachable(StateMachine sm, List<State> lStates)
{
boolean error = false;
if(!sm.hasStates())
return;
for(State s : sm.getStates())
{
if(!lStates.contains(s))
{
if(s.getPosition() != null)
{
getParseResult().addErrorMessage(new ErrorMessage(67,s.getPosition(),s.getName(), sm.getName()));
error = true;
}
}
if (s.hasNestedStateMachines() && !error)
{
for(StateMachine sm2 : s.getNestedStateMachines())
raiseWarningUnreachable(sm2, lStates);
}
}
}
private void checkAttributeConflictImmutableAutounique(){
if(attributeAutouniqueImmutable != null && attributeAutouniqueImmutable.size() > 0)
{
for(Map.Entry<Token, UmpleClass> entry : attributeAutouniqueImmutable.entrySet())
{
String attrNameCapital = Character.toUpperCase(entry.getKey().getValue("name").charAt(0)) + entry.getKey().getValue("name").substring(1);
if(entry.getKey().getValue("autounique") != null)
{
for(Attribute attr : entry.getValue().getAttributes())
{
if(attr.getName().equals("next"+attrNameCapital))
{
getParseResult().addErrorMessage(new ErrorMessage(38,attr.getPosition(),attr.getName(),"autounique",entry.getKey().getValue("name")));
}
}
}
if(entry.getKey().getValue("modifier") !=null)
{
for(Attribute attr : entry.getValue().getAttributes())
{
if(attr.getName().equals("canSet"+attrNameCapital)||attr.getName().equals("can_set_"+entry.getKey().getValue("name")))
{
getParseResult().addErrorMessage(new ErrorMessage(38,attr.getPosition(),attr.getName(),"immutable",entry.getKey().getValue("name")));
}
}
}
}
}
}
private void postTokenInterfaceAnalysis()
{
addUnlinkedInterfaceExtends();
checkExtendsForCyclesInterface();
}
private List <UmpleInterface> recursiveCycleCheckInterface(List <UmpleInterface> extend,UmpleInterface parent, HashMap<UmpleInterface, Boolean> map)
{
List <UmpleInterface> temp = new ArrayList <UmpleInterface>();
if(extend == null|| extend.isEmpty())
return temp;
for (UmpleInterface I: extend){
if(map.containsKey(I)){
temp.add(I);
return temp;
}
map.put(I, true);
for (UmpleInterface EI: I.getExtendsInterface()){
if(parent.equals(EI)){
temp.add(EI);
return temp;
}
}
List <UmpleInterface> temptemp = recursiveCycleCheckInterface(I.getExtendsInterface(), parent, map);
temp.addAll(temptemp);
}
return temp;
}
private void checkExtendsForCyclesInterface()
{
for(UmpleInterface I : model.getUmpleInterfaces())
{
HashMap<UmpleInterface, Boolean> vistedMap = new HashMap<UmpleInterface, Boolean>();
if(I.getExtendsInterface() != null)
{
if(recursiveCycleCheckInterface(I.getExtendsInterface(), I, vistedMap).contains(I))
{
Token t = I.getExtendsToken();
if(t.getValue().equals(I.getName()))
getParseResult().addErrorMessage(new ErrorMessage(11,t.getPosition(),"Interface",I.getName()));
else
getParseResult().addErrorMessage(new ErrorMessage(12,t.getPosition(),"Interface",t.getValue(),I.getName()));
}
}
}
}
private UmpleClass recursiveCycleCheck(UmpleClass extend, UmpleClass parent, HashMap<UmpleClass, Boolean> map)
{
UmpleClass temp = null;
if(extend == null)
return null;
if(map.containsKey(extend))
return extend;
map.put(extend, true);
if(parent.equals(extend.getExtendsClass()))
return extend.getExtendsClass();
temp = recursiveCycleCheck(extend.getExtendsClass(), parent, map);
return temp;
}
private void checkExtendsForCycles()
{
for(UmpleClass C : model.getUmpleClasses())
{
HashMap<UmpleClass, Boolean> vistedMap = new HashMap<UmpleClass, Boolean>();
if(C.getExtendsClass() != null)
{
if(C.equals(recursiveCycleCheck(C.getExtendsClass(), C, vistedMap)))
{ model.setDistributeOn(false);
Token t = C.getExtendsToken();
if(t.getValue().equals(C.getName()))
getParseResult().addErrorMessage(new ErrorMessage(11,t.getPosition(),"Class",C.getName()));
else
getParseResult().addErrorMessage(new ErrorMessage(12,t.getPosition(),"Class",t.getValue(),C.getName()));
}
}
}
}
// Check for classes that should be abstract
private void checkAbstractClass(){
for(UmpleClass uClass : model.getUmpleClasses())
{
if(uClass.getIsAbstract())
{
// no work needs to be done on this class.
continue;
}
if(uClass.getUnimplementedMethods().length > 0)
{
uClass.setIsAbstract(true);
continue;
}
// Check if class should be abstract based on properties of ancestors
HashMap<String, Integer> uChildren = new HashMap<String, Integer>();
checkAbstractClassRecursive(uClass, uChildren);
}
}
/*
* Checks for unimplemented methods in ancestor classes
* against the implemented methods in aClass. if neither
* aClass nor its ancestors implemented a method, then
* aClass must be abstract.
*/
private Method[] checkAbstractClassRecursive(UmpleClass aClass, HashMap<String, Integer> aChildren){
if(aClass.getExtendsToken() == null || aClass.getExtendsClass() == null || aChildren.get(aClass.getName()) != null)
{
return aClass.getUnimplementedMethods();
}
aChildren.put(aClass.getName(), 1);
Method[] ancestorUnimplementedMethods = checkAbstractClassRecursive(aClass.getExtendsClass(), aChildren);
for(Method unimplementedMethod : ancestorUnimplementedMethods)
{
if(!aClass.hasMethod(unimplementedMethod))
{
aClass.addUnimplementedMethod(unimplementedMethod);
}
}
if(aClass.getUnimplementedMethods().length > 0)
{
aClass.setIsAbstract(true);
}
return aClass.getUnimplementedMethods();
}
// Check for the existence of a a parent class
private void checkExtendsClass(){
for(UmpleClass child : model.getUmpleClasses())
{
if(child.getExtendsToken() != null)
{
if (child.getExtendsClass() != null )
{
// check to make sure attribute doesn't share a name with inherited attribute - F.K.
//Issue 578
List<Attribute> attRepetition = new ArrayList<Attribute>();
for(Attribute aAttribute : child.getAttributes())
{
int limit = 100; // prevent infinite loops
UmpleClass extend = child.getExtendsClass();
while(extend!=null && child != extend && limit > 0)
{
limit--;
for(Attribute extendAttr : extend.getAttributes())
{
String currentName = aAttribute.getName();
if (extendAttr.getName().equals(currentName))
{
if(extendAttr.getType().equals(aAttribute.getType()))
{
if(aAttribute.getValue() == null)
attRepetition.add(aAttribute);
else
aAttribute.setIsRefinement(true);
}
else
{
attRepetition.add(aAttribute);
setFailedPosition(aAttribute.getPosition(), 44, child.getName(), currentName, extend.getName());
}
}
}
extend = extend.getExtendsClass();
}
}
for(Attribute att : attRepetition)
child.deleteAttribute(att);
continue;
}
else{
Token t = child.getExtendsToken();
getParseResult().addErrorMessage(new ErrorMessage(33,t.getPosition(),t.getValue(),child.getName()));
}
}
}
}
/*
* Analyzes all associations that are part of the given token indicated to be related to an association.
*
* @param associationToken The token indicated to be an association or association Class where sub tokens will be analyzed from to further
* analyze the individual associations.
*/
private void analyzeAllAssociations(Token associationToken)
{
String name = associationToken.getValue("name");
// Go through every token that is a child of the current token (all associations part of this association).
for(Token token : associationToken.getSubTokens()){
boolean isAssociationToken = token.is("association");
if (token.is("mixsetDefinition"))
{
analyzeMixsetBodyToken(token);
}
//Issue 213/131: [association] elements inside associationClasses generate 2 associations instead of one
if (isAssociationToken && associationToken.is("associationClassDefinition")) {
for (Token t : token.getSubTokens()) {
if (t.is("associationEnd")) {
analyzeAssociation(t, "");
}
}
} else if (isAssociationToken || token.is("singleAssociationEnd")){
Association association = analyzeAssociation(token, "");
if(isAssociationToken && association != null){
association.setName(name);
}
}
if (!getParseResult().getWasSuccess()) { return; }
}
}
/*
* Analyzes a class token to populate an Umple class.
*
* This is also where the list of currently parsed comments will be added to the Umple class.
*
* @param classToken The token which contains the data to be analyzed to populate an Umple class.
*
* @return An Umple class populated with data based on the analysis of the class token.
*/
private UmpleClass analyzeClass(Token classToken)
{
//Reset number of activeObjects with each class definition
this.numberOfActiveObjects = 1;
String className = classToken.getValue("name").split(" ")[classToken.getValue("name").split(" ").length-1];
//Check to ensure the name is valid (starts with a letter, and only contains letters, numbers, or underscores
if (Token.isValidIdentifier(className, "[A-Za-z]") != true) {
setFailedPosition(classToken.getPosition(), 100, className);
}
else if ( className.matches("[a-z].*") ){ // Warn when class name does not start with a capital letter.
setFailedPosition(classToken.getPosition(), 101, className);
}
UmpleClass aClass;
//Issue 213: UmpleClass can be an AssociationClass
if(classToken.is("associationClassDefinition")){
aClass = model.addAssociationClass(classToken.getValue("name"));
}else{
aClass = model.addUmpleClass(classToken.getValue("name"));
}
if ( classToken.is("classDefinition") && "external".equals(aClass.getModifier()) )
aClass.setModifier(""); // Remove the external modifier if a non-external specification of this class is found.
Position thePosition = classToken.getPosition();
Position endPosition = classToken.getEndPosition();
// Set the original .ump file and line number
aClass.addPosition(thePosition);
aClass.addEndPosition(endPosition);
// Add all the comments in the comment list to the Umple class.
// But add them before any umplesource special comments
int regularCommentCountEnd = 0;
for (Comment c : aClass.getComments()) {
if(c.getText().startsWith("@umplesource")) break;
regularCommentCountEnd++;
}
for (Comment c : lastComments)
{
aClass.addCommentAt(c,regularCommentCountEnd);
regularCommentCountEnd++;
}
// Add special position comment at the end if @outputumplesource had been
// detected earlier in a comment
if(outputUmpleSource == true) {
String path = null;
if( thePosition.getFilename() == null ){
path = "";
}else{
path = Paths.get(thePosition.getFilename()).getFileName().toString();
}
aClass.addComment(new Comment("@umplesource " + path +" "+thePosition.getLineNumber()));
}
// If the "abstract" keyword is parsed, make the Umple class an abstract class.
if (classToken.getValue("abstract") != null)
{
boolean wasSet = aClass.setIsAbstract(true);
// Ensure the value was set.
if (wasSet == false)
{
setFailedPosition(classToken.getPosition(), 0, "Unable to make class abstract!");
}
}
addExtendsTo(classToken, aClass, unlinkedExtends, unlinkedExtendsTokens);
// If the "singleton" keyword is parsed, make the Umple class a singleton.
if (classToken.getValue("singleton") != null)
{
aClass.setIsSingleton(true);
}
// If the "Distributable" keyword is parsed, make the Umple class Distributable.
if (classToken.getValue("distributable") != null)
{
aClass.setIsDistributable(true);
if(classToken.getValue("distributeTech")!=null)
aClass.setDistributeTechnology(classToken.getValue("distributeTech"));
}
if (classToken.getValue("proxyPattern")!=null)
{
aClass.setHasProxyPattern(true);
aClass.setNeedsDefaultInterface(true);
}
if(!"".equals(aClass.getPackageName()) && !currentPackageName.equals(aClass.getPackageName())){
setFailedPosition(classToken.getPosition(), 30, aClass.getName(), currentPackageName);
}
if("".equals(aClass.getPackageName())){
String pkgName = getCurrentNameSpace(classToken);
aClass.setPackageName( pkgName!="" ? pkgName : currentPackageName);
}
if (aClass.getIsSingleton())
{
classToken.setName(classToken.getName());
}
if (classToken.getValue("immutable") != null)
{
boolean wasSet = aClass.setImmutable();
if (!wasSet)
{
// Future-proofing: currently all paths cause wasSet to be true
setFailedPosition(classToken.getPosition(), 14, classToken.getName());
}
}
if(classToken.is("associationClassDefinition")) {
analyzeAllAssociations(classToken);
}
analyzeAllTokens(classToken,aClass);
return aClass;
}
/*
* Takes an Umple classifier and analyzes a classifier token to add classifiers which extend it.
*
* @param classifierToken The token to be analyzed to add subclasses to the specified Umple classifier.
* @param aClassifier The Umple classifier for which subclasses will be added.
*/
private void addExtendsTo(Token classifierToken, UmpleClassifier aClassifier, Map <UmpleClassifier,List <String>> unlinkedExtends, Map <UmpleClassifier, List<Token>> unlinkedExtendsTokens)
//private void addExtendsTo(Token classToken, UmpleClassifier aClassifier)
{
List<String> extendsList = new ArrayList<String>();
List<Token> extendsTokenList = new ArrayList<Token>();
String extendName="";
// Go through all sub-tokens of the class token to add subclasses related to the Umple class.
for (Token extendsToken : classifierToken.getSubTokens())
{
if (extendsToken.getValue("extendsName") != null||extendsToken.getValue("extendsNames")!=null)
{
String name = extendsToken.getValue("extendsName") != null?extendsToken.getValue("extendsName"):extendsToken.getValue("extendsNames");
extendsList.add(name);
extendsTokenList.add(extendsToken); // With this line we really don't need the above, todo: refactor
if (unlinkedExtends.containsKey(aClassifier)){
if (!unlinkedExtends.get(aClassifier).contains(name)) unlinkedExtends.get(aClassifier).add(name);
} else{
unlinkedExtends.put(aClassifier, extendsList);
}
if (unlinkedExtendsTokens.containsKey(aClassifier)){
if (!unlinkedExtendsTokens.get(aClassifier).contains(extendsToken)) unlinkedExtendsTokens.get(aClassifier).add(extendsToken);
} else{
unlinkedExtendsTokens.put(aClassifier, extendsTokenList);
}
extendName = extendsToken.getValue("extendsName");
} else if (extendsToken.getValue("gTemplateParameter") !=null ){
processGTemplateParameterAssignment(extendsToken, aClassifier, extendName);
}
}
//Checks list of extends tokens to prevent multiple class inheritance
if(numberOfExtendsClass(unlinkedExtendsTokens.get(aClassifier)) > 1)
{
Token t = classifierToken;
String otherClass = "indicated here";
Position thePosition = new Position("",0,0,0);
if(extendsTokenList.size()>0) {
t = extendsTokenList.get(0);
otherClass = t.getValue();
thePosition = t.getPosition();
}
setFailedPosition(thePosition, 34, aClassifier.getName(), otherClass);
}
}
//Returns the number of umple class in extends list (extList)
private int numberOfExtendsClass(List<Token> extList)
{
if (extList == null) {
return 0;
}
Set<String> tokenSet = new HashSet<>();
for(Token t : extList)
{
if(isAnUmpleClass(t.getValue("extendsName"))) {
tokenSet.add(t.getValue("extendsName"));
}
}
return tokenSet.size();
}
//This method checks if an umple element with name "name" is an umple class
private boolean isAnUmpleClass(String name)
{
for(UmpleClass aClass : model.getUmpleClasses())
{
if(aClass != null)
{
String nam = aClass.getName();
if(nam.equals(name))
return true;
}
}
return false;
}
private UmpleClass analyzeExternal(Token externalToken)
{
// Check to see if there is an existing class
UmpleClass existingClass = model.getUmpleClass(externalToken.getValue("name"));
UmpleClass aClass = analyzeClass(externalToken);
// Only set the modifier to external if there is not a class defined with the same name
if ( existingClass == null )
aClass.setModifier("external");
return aClass;
}
private UmpleInterface analyzeExternalInterface(Token externalToken)
{
UmpleInterface anInterface = analyzeInterface(externalToken);
anInterface.setModifier("external");
return anInterface;
}
private UmpleInterface analyzeInterface(Token interfaceToken)
{
String interfaceName = interfaceToken.getValue("name");
//Check to ensure the name is valid (starts with a letter, and only contains letters, numbers, or underscores
if (Token.isValidIdentifier(interfaceName, "[A-Za-z|@]") != true) {
if(!interfaceName.matches("[A-Za-z|@]+<.+>"))
{
setFailedPosition(interfaceToken.getPosition(), 110, interfaceName);
}
}
else if ( interfaceName.matches("[a-z].*") ){ // Warn when interface name doesn't start with a capital letter.
setFailedPosition(interfaceToken.getPosition(), 111, interfaceName);
}
UmpleInterface anInterface = new UmpleInterface(interfaceToken.getValue("name"),model);
model.addUmpleInterface(anInterface);
Position thePosition = interfaceToken.getPosition();
Position endPosition = interfaceToken.getEndPosition();
// Set the original .ump file and line number
anInterface.addPosition(thePosition);
anInterface.addEndPosition(endPosition);
// Add all the comments in the comment list to the Umple class.
// But add them before any umplesource special comments
int regularCommentCountEnd = 0;
for (Comment c : anInterface.getComments()) {
if(c.getText().startsWith("@umplesource")) break;
regularCommentCountEnd++;
}
for (Comment c : lastComments)
{
anInterface.addCommentAt(c,regularCommentCountEnd);
regularCommentCountEnd++;
}
// Add special position comment at the end if @outputumplesource had been
// detected earlier in a comment
if(outputUmpleSource == true) {
String path = null;
if( thePosition.getFilename() == null ){
path = "";
}else{
path = Paths.get(thePosition.getFilename()).getFileName().toString();
}
anInterface.addComment(new Comment("@umplesource " + path +" "+thePosition.getLineNumber()));
}
if(!"".equals(anInterface.getPackageName()) && !currentPackageName.equals(anInterface.getPackageName())){
setFailedPosition(interfaceToken.getPosition(), 30, anInterface.getName(), currentPackageName);
}
if("".equals(anInterface.getPackageName())){
String pkgName = getCurrentNameSpace(interfaceToken);
anInterface.setPackageName( pkgName!="" ? pkgName : currentPackageName);
}
analyzeInterface(interfaceToken,anInterface);
return anInterface;
}
private void analyzeInterface(Token interfaceToken, UmpleInterface aInterface)
{
for(Token token : interfaceToken.getSubTokens())
{
if (token.is("depend"))
{
Depend d = new Depend(token.getValue());
aInterface.addDepend(d);
}
if (token.is("interfaceMemberDeclaration"))
{
analyzeInterfaceMembers(token, aInterface);
}
else if (token.is("elementPosition"))
{
aInterface.setCoordinates(new Coordinate(token.getIntValue("x"),token.getIntValue("y"), token.getIntValue("width"), token.getIntValue("height")));
}
}
}
private void addUnlinkedInterfaceExtends(){
for (UmpleClassifier c : unlinkedInterfaceExtends.keySet())
{
UmpleInterface child = null; //unlinkedInterfaceExtends guaranteed to contain only UmpleInterfaces
if (c instanceof UmpleInterface){
child = (UmpleInterface) c;
}
List<String> extendsNames = unlinkedInterfaceExtends.get(child);
List<Token> extendsToken = unlinkedExtendsTokensInterface.get(child);
if (extendsNames == null)
{
continue;
}
for (int i=0; i < extendsNames.size();i++)
{
String extendName= extendsNames.get(i);
UmpleInterface uInterface= model.getUmpleInterface(extendName);
//Issue 595
if(uInterface == null)
{
getParseResult().addErrorMessage(new ErrorMessage(39,extendsToken.get(i).getPosition(),child.getName(),extendName));
}
else
{
boolean wasSet = child.addExtendsInterface(uInterface);
if (!wasSet)
{
Position pos;
try
{
pos = extendsToken.get(i).getPosition();
}
catch(Exception e)
{
pos = new Position("",0,0,0);
}
setFailedPosition(pos, 16, child.getName(), uInterface.getName());
return;
}
try
{
child.setExtendsToken(extendsToken.get(i));
}
catch(Exception e){}
}
}
}
}
private void analyzeInterfaceMembers(Token interfaceMemberToken, UmpleInterface aInterface)
{
for(Token childToken : interfaceMemberToken.getSubTokens())
{
addExtendsTo(interfaceMemberToken, aInterface, unlinkedInterfaceExtends, unlinkedExtendsTokensInterface);
if(childToken.is("abstractMethodDeclaration"))
{
analyzeMethod(childToken, aInterface);
}
else if (childToken.is("distributableInterface"))
{
analyzeDistributableInterface(childToken, aInterface);
}
else if (childToken.is("constantDeclaration"))
{
analyzeConstant(childToken, aInterface);
}
else if (childToken.is("constantDeclarationDeprecated"))
{
setFailedPosition(childToken.getPosition(), 901);
analyzeConstant(childToken, aInterface);
}
else if (childToken.is("displayColor"))
{ // Note: See near clone in UmpleInternalParser_CodeLayout.ump
String theColor = childToken.getValue("colorValue");
if(theColor.startsWith("=")) theColor=theColor.substring(1,theColor.length());
if(!theColor.startsWith("\"")) theColor= "\""+theColor;
if(!theColor.endsWith("\"")) theColor= theColor+"\"";
aInterface.setDisplayColor(theColor);
}
else if (childToken.is("extraCode") || childToken.is("exception"))
{
if (childToken.is("extraCode"))
{
setFailedPosition(childToken.getPosition(), 1007);
aInterface.appendExtraCode(childToken.getValue("extraCode"));
}
else if (childToken.is("exception"))
{
analyzeException(childToken, aInterface);
}
}
}
}
private void analyzeAssociationClass(Token classToken)
{
//test if Association class has at least 1 association or more than one singleEndAssociation
List<Token> subtokens = classToken.getSubTokens();
int singleAssocNumber = 0;
int assocNumber = 0;
for(Token t : subtokens){
if(t.is("singleAssociationEnd")){
singleAssocNumber++;
}else if (t.is("association")){
assocNumber++;
}
}
if(singleAssocNumber == 1 || (assocNumber == 0 && singleAssocNumber == 0)){
setFailedPosition(classToken.getPosition(), 8, classToken.getValue("name"));
return;
}
analyzeClass(classToken);
}
private boolean verifyClassesInUse()
{
for(Map.Entry<Position, String> e : positionToClassNameReference.entrySet())
{
boolean isAClass = model.getUmpleClass(e.getValue()) != null;
boolean isAInterface = model.getUmpleInterface(e.getValue()) != null;
boolean isATrait = model.getUmpleTrait(e.getValue()) != null;
boolean isTraitTypeParameter = model.getUmpleTraitTypeParameter(e.getValue());
if (!isATrait && !isAClass && !isAInterface && !isTraitTypeParameter) //item referenced not a class or interface or a trait or a type parameter
{
//TODO traits' error
UmpleClass aClass = model.addUmpleClass(e.getValue());
aClass.setPackageName(model.getDefaultNamespace());
setFailedPosition(e.getKey(), 5, e.getValue());
return false;
}
}
return true;
}
private boolean associationIsBetweenClassAndInterface (Association a){
AssociationEnd myEnd = a.getEnd(0);
AssociationEnd yourEnd = a.getEnd(1);
UmpleClass myClass = model.getUmpleClass(myEnd.getClassName());
UmpleInterface yourClass = model.getUmpleInterface(yourEnd.getClassName());
if (myClass != null && yourClass != null ){ //association is between class and interface
return true;
}
return false;
}
private boolean associationIsBetweenClassAndAbstractClass (Association a) {
AssociationEnd endA = a.getEnd(0);
AssociationEnd endB = a.getEnd(1);
UmpleClass classA = model.getUmpleClass(endA.getClassName());
UmpleClass classB = model.getUmpleClass(endB.getClassName());
if (classA != null && classB != null) {
if (classB.getIsAbstract() && !classA.getIsAbstract()) {
return true;
}
}
return false;
}
private boolean associationIsBetweenAbstractClassAndClass (Association a) {
AssociationEnd endA = a.getEnd(0);
AssociationEnd endB = a.getEnd(1);
UmpleClass classA = model.getUmpleClass(endA.getClassName());
UmpleClass classB = model.getUmpleClass(endB.getClassName());
if (classA != null && classB != null) {
if (!classB.getIsAbstract() && classA.getIsAbstract()) {
return true;
}
}
return false;
}
private boolean associationIsBetweenClassAndTrait(Association a){
AssociationEnd myEnd = a.getEnd(0);
AssociationEnd yourEnd = a.getEnd(1);
UmpleTrait myClass = model.getUmpleTrait(myEnd.getClassName());
UmpleClass yourClass = model.getUmpleClass(yourEnd.getClassName());
if (myClass != null && yourClass != null ){ //association is between class and interface
return true;
}
return false;
}
private void addAutogeneratedMethodsForAssociations()
{
for (Association a : model.getAssociations())
{
addAPIMethodsForAssociation(a);
}
}
private void addAutogeneratedMethodsForStateMachines()
{
for (UmpleClass aClass : getModel().getUmpleClasses())
{
for(StateMachine sm : aClass.getStateMachines())
{
addAPIMethodsForStateMachine(sm);
}
}
}
private void addUnlinkedAssociationVariables()
{
for (AssociationVariable av : unlinkedAssociationVariables)
{
AssociationVariable relatedAv = av.getRelatedAssociation();
UmpleClass aClass = model.getUmpleClass(av.getType());
UmpleClass bClass = model.getUmpleClass(relatedAv.getType());
if (aClass == null || bClass == null){ //Association is between Class and Interface
continue;
}
Association assoc = bClass.getAssociation(bClass.indexOfAssociationVariable(av));
boolean added = aClass.addAssociationVariable(relatedAv);
if (!added)
{
if ((!aClass.isImmutable() && !av.getRelatedAssociation().getIsNavigable()) || (!bClass.isImmutable() && !av.getIsNavigable()))
{
setFailedPosition(assoc.getTokenPosition(),13);
}
else { setFailedPosition(assoc.getTokenPosition(),18); }
return;
}
aClass.addAssociation(assoc);
// Set the proper end number for both association variable
AssociationEnd end0 = assoc.getEnd(0);
AssociationEnd end1 = assoc.getEnd(1);
if (end0.getClassName().equals(aClass.getName()) && end1.getClassName().equals(bClass.getName()))
{
av.setRelevantEnd(0);
relatedAv.setRelevantEnd(1);
} else {
av.setRelevantEnd(1);
relatedAv.setRelevantEnd(0);
}
if (av.getIsNavigable())
{
bClass.addReferencedPackage(aClass.getPackageName());
}
if (av.getRelatedAssociation().getIsNavigable())
{
aClass.addReferencedPackage(bClass.getPackageName());
}
}
}
private boolean isUmpleClass(String elementName)
{
return (model.getUmpleInterface(elementName) != null) ? false: true;
}
private void addUnlinkedExtends()
{
for (UmpleClassifier c : unlinkedExtends.keySet())
{
UmpleClass child = null; // unlinkedExtends guaranteed to contain only UmpleClasses
if (c instanceof UmpleClass){
child = (UmpleClass) c;
}
List<String> extendsNames = unlinkedExtends.get(child);
List<Token> extendsToken = unlinkedExtendsTokens.get(child);
if (extendsNames == null)
{
continue;
}
for (int i=0; i < extendsNames.size();i++)
{
String extendName= extendsNames.get(i);
if (isUmpleClass(extendName))
{
if (isUmpleTrait(extendName) )
{
UmpleTrait parent = model.getUmpleTrait(extendName);
boolean wasSet = child.addExtendsTrait(parent);
if (!wasSet)
{
Position pos;
try
{
pos = extendsToken.get(i).getPosition();
}
catch(Exception e)
{
pos = new Position("",0,0,0);
}
// TODO 1: the error code should be chnaged.
setFailedPosition(pos, 16, child.getName(), parent.getName());
return;
}
//---
/*
try
{
child.setExtendsToken(extendsToken.get(i));
}
catch(Exception e){}
*/
}
else
{
UmpleClass parent = model.getUmpleClass(extendName);
boolean wasSet = child.setExtendsClass(parent);
if (!wasSet)
{
Position pos;
try
{
pos = extendsToken.get(i).getPosition();
}
catch(Exception e)
{
pos = new Position("",0,0,0);
}
setFailedPosition(pos, 16, child.getName(), parent.getName());
child.setExtendsToken(extendsToken.get(i)); //Issue 543
return;
}
try
{
child.setExtendsToken(extendsToken.get(i));
checkExtendsClass(); //Issue 543
}
catch(Exception e){}
}
}
else
{
UmpleInterface uInterface= model.getUmpleInterface(extendName);
child.addParentInterface(uInterface);
}
}
}
for(UmpleClass uClass: getModel().getUmpleClasses()) {
addImplementedMethodsFromInterface(getAllParentInterface(uClass), uClass);
}
}
private void addUnlinkedKeys() {
// If a dependency cycle exists, then we can't add keys
List<ErrorMessage> errors = getParseResult().getErrorMessages();
for (ErrorMessage err: errors)
{
int code = err.getErrorType().getErrorCode();
if (code == 11 || code == 12)
{
return;
}
}
for (UmpleClass c : unlinkedKeysTokens.keySet())
{
List<Token> keyTokens = unlinkedKeysTokens.get(c);
List<String> tokensAdded = new ArrayList<String>();
if (keyTokens == null || keyTokens.isEmpty())
{
continue;
}
Boolean tokenMatch;
for (Token token: keyTokens)
{
tokenMatch = false;
String tokenVal = token.getValue();
String finalTokenVal = token.getValue();
// Check the current class and it's inherited classes for the Attribute
UmpleClass classSearch = c;
Attribute att = classSearch.getAttribute(tokenVal);
if (att != null)
{
tokenMatch = true;
// throw warning if attribute is initialized
if (att.getValue() != null && !("null".equals(att.getValue()) || att.getIsDerived()))
{
setFailedPosition(token.getPosition(), 45, att.getName(), c.getName());
}
}
if (c.hasAssociations())
{
AssociationEnd secondEnd;
String secondEndName;
for(Association aAssociation : c.getAssociations())
{
secondEnd = aAssociation.getEnd(1);
secondEndName = secondEnd.getRoleName();
// tokenVal with a lower-case first letter.
String firstLower = Character.toLowerCase(tokenVal.charAt(0)) + (tokenVal.length() > 1 ? tokenVal.substring(1) : "");
if(secondEndName.equals(firstLower))
{
finalTokenVal = firstLower;
tokenMatch = true;
break;
}
}
}
if(c.hasStateMachines())
{
for(StateMachine aStateMachine : c.getStateMachines())
{
if(aStateMachine.getName().equals(tokenVal))
{
tokenMatch = true;
break;
}
}
}
if(c instanceof AssociationClass)
{
AssociationClass assocClass = (AssociationClass) c;
String endName;
for(Association associatedTo: assocClass.getAssociatedTo())
{
endName = associatedTo.getEnd(1).getRoleName();
// tokenVal with a lower-case first letter.
String firstLower = Character.toLowerCase(tokenVal.charAt(0)) + (tokenVal.length() > 1 ? tokenVal.substring(1) : "");
if(endName.equals(firstLower))
{
finalTokenVal = firstLower;
tokenMatch = true;
break;
}
}
}
if(!tokenMatch)
{
setFailedPosition(token.getPosition(), 27, tokenVal, c.getName());
}
c.getKey().addMember(finalTokenVal);
tokensAdded.add(finalTokenVal);
}
//Issue 610: Associaton Classes with incomplete keys must issue warning 1011
if(c instanceof AssociationClass)
{
AssociationClass assocClass = (AssociationClass) c;
String endName;
for(Association associatedTo : assocClass.getAssociatedTo())
{
endName = associatedTo.getEnd(1).getRoleName();
if(!tokensAdded.contains(endName))
{
Token parent = keyTokens.get(0).getParentToken();
setFailedPosition(parent.getPosition(), 1011, c.getName(), endName, tokensAdded.toString());
}
}
}
}
}
private java.util.List<UmpleInterface> getAllParentInterface(UmpleClass uClass){
java.util.ArrayList<UmpleInterface> temp = new java.util.ArrayList<UmpleInterface>(uClass.getParentInterface());
java.util.ArrayList<UmpleClass> uClassChain = new java.util.ArrayList<UmpleClass>();
for (UmpleClass uClassCurrent = uClass; uClassCurrent != null && !uClassChain.contains(uClassCurrent); uClassCurrent = uClassCurrent.getExtendsClass())
{
uClassChain.add(uClassCurrent);
for (UmpleInterface uInterface : uClassCurrent.getParentInterface())
{
if (!temp.contains(uInterface))
{
temp.add(uInterface);
}
}
}
return temp;
}
private void addImplementedMethodsFromInterface(List<UmpleInterface> parentInterfaces, UmpleClass uClass)
{
//GET AND SET METHODS CHECK?
// Fix issue 992
List<UmpleInterface> ancestorInterfaces = new Vector<UmpleInterface>();
Stack<UmpleInterface> toProcess = new Stack<UmpleInterface>();
toProcess.addAll(parentInterfaces);
ancestorInterfaces.addAll(parentInterfaces);
while(!toProcess.isEmpty())
{
parentInterfaces = toProcess.pop().getExtendsInterface();
toProcess.addAll(parentInterfaces);
ancestorInterfaces.addAll(parentInterfaces);
}
for (UmpleInterface pi : ancestorInterfaces)
{
if (pi.hasMethods())
{
for (Method aMethod : pi.getMethods())
{
boolean shouldAddMethod = !isConstructorOrGetSet(uClass, aMethod, true);
if (!(uClass.hasImplementedMethodIncludingWithinParentClasses(aMethod))
&& !(uClass.hasMethodInTraits(aMethod)) && !(uClass.isIsAbstract()) && shouldAddMethod)
{
aMethod.setIsImplemented("".equals(aMethod.getMethodBody().getExtraCode("")));
aMethod.setSource(Method.Source.fAuto);
uClass.addMethod(aMethod);
}
}
}
}
}
/*
* Used to determine if a method is a contructor or a getter/setter.
*
* @param uClass The Umple class for which the method is contained.
* @param aMethod The method which is contained within the Umple class.
*
* @return True if the method is a constructor, getter/setter, false otherwise.
*/
private boolean isConstructorOrGetSet(UmpleClass uClass, Method aMethod, boolean checkingInInterface)
{
//1. Check if method is a getter or setter
if (uClass.hasMethod(aMethod) && isGetterSetter(uClass.getMethod(aMethod)))
return true;
//2. Verify if method to be added is a constructor
if (aMethod.getType().equals("public"))
{
uClass.appendExtraCode(aMethod.toString());
return true;
}
//3. Verify if method from interface is already part of the Class extracode
String match = "public " + aMethod.getType() + " " + aMethod.getName();
if (uClass.getExtraCode().contains(match))
return true;
return false;
}
private boolean isGetterSetter(Method method){
if (method.getName().length() <= 2)
return false;
String accessorName = method.getName().substring(0,3);
return ((accessorName.equals("get")) || (accessorName.equals("set"))) && method.getSource() == Method.Source.fAutoAPI && !method.getIsConstructor();
}
// Same association in subclass with a different role name should emit
// error 19.
// class A {
// 1 sth1 -- * C;
// }
// class B {
// isA A;
// 1 sth2 -- * C;
// }
// class C {
// }
private void checkSubclassSameAssociationDifferentRoleNames() {
for (UmpleClass uClass : model.getUmpleClasses())
{
List<String> visitedSupers = new ArrayList<String>();
UmpleClass superClass = uClass.getExtendsClass();
while (superClass != null)
{
// Prevent cycles.
if (visitedSupers.contains(superClass.getName()))
{
break;
}
if (superClass.getIsAbstract()) {
break;
}
visitedSupers.add(superClass.getName());
for (AssociationVariable assocVar : uClass.getAssociationVariables()) {
// Test whether the super class has the
AssociationVariable supAssocVar = superClass.getAssociationVariable(assocVar.getType(), assocVar.getName());
if (supAssocVar == null) continue;
// The modifiable reference to the current association variable
AssociationVariable curAssocVar = uClass.getAssociationVariable(assocVar.getType(), assocVar.getName());
Association curAssoc = uClass.getAssociation(uClass.indexOfAssociationVariable(curAssocVar));
Association supAssoc = superClass.getAssociation(superClass.indexOfAssociationVariable(supAssocVar));
AssociationEnd curLeftEnd = curAssoc.getEnd(0);
AssociationEnd curRightEnd = curAssoc.getEnd(1);
AssociationEnd supLeftEnd = supAssoc.getEnd(0);
AssociationEnd supRightEnd = supAssoc.getEnd(1);
AssociationEnd curMyEnd, curOtherEnd;
AssociationEnd supMyEnd, supOtherEnd;
// Find out "MyEnd" and "OtherEnd"
if (curLeftEnd.getRoleName().equals(curAssocVar.getName())
&& curLeftEnd.getClassName().equals(curAssocVar.getType())) {
curMyEnd = curRightEnd;
curOtherEnd = curLeftEnd;
} else {
curMyEnd = curLeftEnd;
curOtherEnd = curRightEnd;
}
if (supLeftEnd.getRoleName().equals(supAssocVar.getName())
&& supLeftEnd.getClassName().equals(supAssocVar.getType())) {
supMyEnd = supRightEnd;
supOtherEnd = supLeftEnd;
} else {
supMyEnd = supLeftEnd;
supOtherEnd = supRightEnd;
}
// test the name of "MyEnd"
if (curMyEnd.getRoleName().equals(supMyEnd.getRoleName())) {
continue;
}
// test multiplicity of both associations
Multiplicity curMyMultiplicity = curMyEnd.getMultiplicity();
Multiplicity curOtherMultiplicity = curOtherEnd.getMultiplicity();
Multiplicity supMyMultiplicity = supMyEnd.getMultiplicity();
Multiplicity supOtherMultiplicity = supOtherEnd.getMultiplicity();
if (curMyMultiplicity.getLowerBound() != supMyMultiplicity.getLowerBound()
|| curMyMultiplicity.getUpperBound() != supMyMultiplicity.getUpperBound()
|| curOtherMultiplicity.getLowerBound() != supOtherMultiplicity.getLowerBound()
|| curOtherMultiplicity.getUpperBound() != supOtherMultiplicity.getUpperBound()) {
continue;
}
getParseResult().addErrorMessage(new ErrorMessage(19, curAssoc.getTokenPosition(), curMyEnd.getClassName(), curOtherEnd.getClassName()));
}
superClass = superClass.getExtendsClass();
}
}
}
// Some associations should be ingnored
// Example:
// class A {
// 1 -- * C;
// }
// class B {
// isA A;
// 1 -- * C;
//}
//class C {
//}
// Then the association in B should be ignored.
private void checkIgnoredAssociations() {
for (UmpleClass uClass : model.getUmpleClasses())
{
List<String> visitedSupers = new ArrayList<String>();
UmpleClass superClass = uClass.getExtendsClass();
String[] uClassAssocVarNames = new String[uClass.numberOfAssociationVariables()];
String[] uClassAssocVarClassNames = new String[uClass.numberOfAssociationVariables()];
int numCurAssocVar = 0;
for (AssociationVariable curAssocVar : uClass.getAssociationVariables()) {
uClassAssocVarNames[numCurAssocVar] = curAssocVar.getName();
uClassAssocVarClassNames[numCurAssocVar] = curAssocVar.getType();
numCurAssocVar = numCurAssocVar + 1;
}
while (superClass != null)
{
// Prevent cycles.
if (visitedSupers.contains(superClass.getName()))
{
break;
}
if (superClass.getIsAbstract()) {
break;
}
visitedSupers.add(superClass.getName());
for (int i=0; i < numCurAssocVar; i++) {
AssociationVariable curAssocVar = uClass.getAssociationVariable(uClassAssocVarClassNames[i], uClassAssocVarNames[i]);
AssociationVariable supAssocVar = superClass.getAssociationVariable(curAssocVar.getType(), curAssocVar.getName());
if (supAssocVar == null) continue;
Association curAssoc = uClass.getAssociation(uClass.indexOfAssociationVariable(curAssocVar));
Association supAssoc = superClass.getAssociation(superClass.indexOfAssociationVariable(supAssocVar));
AssociationEnd curLeftEnd = curAssoc.getEnd(0);
AssociationEnd curRightEnd = curAssoc.getEnd(1);
AssociationEnd supLeftEnd = supAssoc.getEnd(0);
AssociationEnd supRightEnd = supAssoc.getEnd(1);
AssociationEnd curMyEnd, curOtherEnd;
AssociationEnd supMyEnd, supOtherEnd;
// Find out "MyEnd" and "OtherEnd"
if (curRightEnd.getRoleName().equals(supRightEnd.getRoleName())
&& curRightEnd.getClassName().equals(supRightEnd.getClassName())) {
curMyEnd = curLeftEnd;
curOtherEnd = curRightEnd;
supMyEnd = supLeftEnd;
supOtherEnd = supRightEnd;
} else if (curLeftEnd.getRoleName().equals(supRightEnd.getRoleName())
&& curLeftEnd.getClassName().equals(supRightEnd.getClassName())) {
curMyEnd = curRightEnd;
curOtherEnd = curLeftEnd;
supMyEnd = supLeftEnd;
supOtherEnd = supRightEnd;
} else if (curRightEnd.getRoleName().equals(supLeftEnd.getRoleName())
&& curRightEnd.getClassName().equals(supLeftEnd.getClassName())) {
curMyEnd = curLeftEnd;
curOtherEnd = curRightEnd;
supMyEnd = supRightEnd;
supOtherEnd = supLeftEnd;
} else if (curLeftEnd.getRoleName().equals(supLeftEnd.getRoleName())
&& curLeftEnd.getClassName().equals(supLeftEnd.getClassName())) {
curMyEnd = curRightEnd;
curOtherEnd = curLeftEnd;
supMyEnd = supRightEnd;
supOtherEnd = supLeftEnd;
} else {
continue;
}
// test the type of "MyEnd"
if (!curMyEnd.getClassName().equals(uClass.getName())
|| !supMyEnd.getClassName().equals(superClass.getName())) {
continue;
}
// test the role name of "MyEnd"
if (!(curMyEnd.getIsDefaultRoleName() && supMyEnd.getIsDefaultRoleName())
&& !curMyEnd.getRoleName().equals(supMyEnd.getRoleName())) {
continue;
}
// test multiplicity of both associations
Multiplicity curMyMultiplicity = curMyEnd.getMultiplicity();
Multiplicity curOtherMultiplicity = curOtherEnd.getMultiplicity();
Multiplicity supMyMultiplicity = supMyEnd.getMultiplicity();
Multiplicity supOtherMultiplicity = supOtherEnd.getMultiplicity();
if (curMyMultiplicity.getLowerBound() != supMyMultiplicity.getLowerBound()
|| curMyMultiplicity.getUpperBound() != supMyMultiplicity.getUpperBound()
|| curOtherMultiplicity.getLowerBound() != supOtherMultiplicity.getLowerBound()
|| curOtherMultiplicity.getUpperBound() != supOtherMultiplicity.getUpperBound()) {
continue;
}
// find the associated class
UmpleClass associatedClass = model.getUmpleClass(curOtherEnd.getClassName());
AssociationVariable associatedAV = associatedClass.getAssociationVariable(curMyEnd.getClassName(), curMyEnd.getRoleName());
if (associatedAV == null) {
continue;
}
Association associatedAssociation = associatedClass.getAssociation(associatedClass.indexOfAssociationVariable(associatedAV));
uClass.removeAssociation(curAssoc);
uClass.removeAssociationVariable(curAssocVar);
associatedClass.removeAssociation(associatedAssociation);
associatedClass.removeAssociationVariable(associatedAV);
}
superClass = superClass.getExtendsClass();
}
}
}
private void createSpecializedLinks()
{
// Don't do it if we have already detected cycles
List<ErrorMessage> errors = getParseResult().getErrorMessages();
for (ErrorMessage err: errors)
{
int code = err.getErrorType().getErrorCode();
if (code == 11 || code == 12)
{
return;
}
}
ArrayList<Association> visited = new ArrayList<Association>();
Boolean boundsOK, subclassOK, namesOK, reverseEnds;
String aLName, aRName, bLName, bRName;
ArrayList<String> leftHierarchy, rightHierarchy;
UmpleClass currentLeftClass, currentRightClass, parentLeftClass, parentRightClass;
AssociationEnd bLeftEnd, bRightEnd, aLeftEnd, aRightEnd;
for (Association A : model.getAssociations())
{
if (A.getIsSpecialization())
{
continue;
}
aLeftEnd = A.getEnd(0);
aRightEnd = A.getEnd(1);
// symmetric reflexive? no thanks -- not a specializable case (see below -- A -- A twice)
if (aLeftEnd.getClassName().equals(aRightEnd.getClassName()))
{
continue;
}
aLName = aLeftEnd.getRoleName();
aRName = aRightEnd.getRoleName();
// If one of the ends of the association are to a trait or an interface, the logic currently falls apart.
// Given that, we will skip these two situations.
if (model.getUmpleInterface(aLeftEnd.getClassName()) != null || model.getUmpleInterface(aRightEnd.getClassName()) != null)
{
continue;
}
if (model.getUmpleTrait(aLeftEnd.getClassName()) != null || model.getUmpleTrait(aRightEnd.getClassName()) != null)
{
continue;
}
for (Association B : visited)
{
if (B.getIsSpecialization())
{
continue;
}
leftHierarchy = new ArrayList<String>();
rightHierarchy = new ArrayList<String>();
bLeftEnd = B.getEnd(0);
bRightEnd = B.getEnd(1);
bLName = bLeftEnd.getRoleName();
bRName = bRightEnd.getRoleName();
// if either end is an interface or a trait, continue (see above)
if (model.getUmpleInterface(bLeftEnd.getClassName()) != null || model.getUmpleInterface(bRightEnd.getClassName()) != null)
{
continue;
}
if (model.getUmpleTrait(bLeftEnd.getClassName()) != null || model.getUmpleTrait(bRightEnd.getClassName()) != null)
{
continue;
}
// if A -- B twice, we don't want that.
if (aLeftEnd.getClassName().equals(bLeftEnd.getClassName()) && aRightEnd.getClassName().equals(bRightEnd.getClassName()))
{
continue;
}
// reset the appropriate booleans
namesOK = false;
boundsOK = false;
subclassOK = false;
reverseEnds = false;
// build class hierarchy lists
currentLeftClass = model.getUmpleClass(aLeftEnd.getClassName());
parentLeftClass = currentLeftClass.getExtendsClass();
currentRightClass = model.getUmpleClass(aRightEnd.getClassName());
parentRightClass = currentRightClass.getExtendsClass();
leftHierarchy.add(currentLeftClass.getName());
rightHierarchy.add(currentRightClass.getName());
while (parentLeftClass != null && !parentLeftClass.getIsAbstract())
{
leftHierarchy.add(parentLeftClass.getName());
parentLeftClass = parentLeftClass.getExtendsClass();
}
while (parentRightClass != null && !parentRightClass.getIsAbstract())
{
rightHierarchy.add(parentRightClass.getName());
parentRightClass = parentRightClass.getExtendsClass();
}
parentRightClass = model.getUmpleClass(bRightEnd.getClassName());
parentLeftClass = model.getUmpleClass(bLeftEnd.getClassName());
if ( leftHierarchy.contains(parentLeftClass.getName()) && rightHierarchy.contains(parentRightClass.getName()))
{
subclassOK = true;
}
else if (rightHierarchy.contains(parentLeftClass.getName()) && leftHierarchy.contains(parentRightClass.getName()))
{
reverseEnds = true;
subclassOK = true;
}
if (!subclassOK) {
continue;
}
// now we need to see if the names match
if (!reverseEnds) {
if ((aLName.equals(bLName) || (aLeftEnd.getIsDefaultRoleName() && bLeftEnd.getIsDefaultRoleName()))
&& (aRName.equals(bRName) || (aRightEnd.getIsDefaultRoleName() && bRightEnd.getIsDefaultRoleName()))) {
namesOK = true;
}
} else {
if ((aLName.equals(bRName) || (aLeftEnd.getIsDefaultRoleName() && bRightEnd.getIsDefaultRoleName()))
&& (aRName.equals(bLName) || (aRightEnd.getIsDefaultRoleName() && bLeftEnd.getIsDefaultRoleName()))) {
namesOK = true;
}
}
if (!namesOK) {
continue;
}
// Align the left and right ends before doing bound check
AssociationEnd aFirstEnd, aSecondEnd, bFirstEnd, bSecondEnd;
if (!reverseEnds) {
aFirstEnd = aLeftEnd;
aSecondEnd = aRightEnd;
bFirstEnd = bLeftEnd;
bSecondEnd = bRightEnd;
} else {
aFirstEnd = aLeftEnd;
aSecondEnd = aRightEnd;
bFirstEnd = bRightEnd;
bSecondEnd = bLeftEnd;
}
int a1LB, a1UB, a2LB, a2UB, b1LB, b1UB, b2LB, b2UB;
a1LB = aFirstEnd.getMultiplicity().getLowerBound();
a1UB = aFirstEnd.getMultiplicity().getUpperBound();
a2LB = aSecondEnd.getMultiplicity().getLowerBound();
a2UB = aSecondEnd.getMultiplicity().getUpperBound();
b1LB = bFirstEnd.getMultiplicity().getLowerBound();
b1UB = bFirstEnd.getMultiplicity().getUpperBound();
b2LB = bSecondEnd.getMultiplicity().getLowerBound();
b2UB = bSecondEnd.getMultiplicity().getUpperBound();
Boolean firstLowerBoundOK, firstUpperBoundOK, secondLowerBoundOK, secondUpperBoundOK;
firstLowerBoundOK = false;
firstUpperBoundOK = false;
secondLowerBoundOK = false;
secondUpperBoundOK = false;
firstLowerBoundOK = (b1LB <= a1LB);
firstUpperBoundOK = (b1UB >= a1UB);
secondLowerBoundOK = (b2LB <= a2LB);
secondUpperBoundOK = (b2UB >= a2UB);
Boolean firstMulChToOne = false;
Boolean secondMulChToOne = false;
Boolean firstMulChToN = false;
Boolean secondMulChToN = false;
Boolean firstNeedsSetCode = false;
Boolean secondNeedsSetCode = false;
if (b1UB != 1 && a1UB == 1)
firstMulChToOne = true;
if (b2UB != 1 && a2UB == 1)
secondMulChToOne = true;
if (a1UB == a1LB && b1LB == 0 && b1UB > 1) // && aLUB > 1
firstMulChToN = true;
if (a2UB == a2LB && b2LB == 0 && b2UB > 1) // && aRUB > 1
secondMulChToN = true;
// changed to MN (or M*) -- this may need to change when set method generation is fixed
if (a1UB >= a1LB && a1LB != 0 && a1UB > 1 && ((b1LB == 0 && b1UB > 1) || b1UB == -1))
firstMulChToN = true;
// changed to MN (or M*)
if (a2UB >= a2LB && a2LB != 0 && a2UB > 1 && ((b2LB == 0 && b2UB > 1) || b2UB == -1))
secondMulChToN = true;
// special case for * multiplicity
if ( b1UB == -1 )
{
firstUpperBoundOK = true;
}
if ( b2UB == -1 )
{
secondUpperBoundOK = true;
}
if ( b1UB == -1 || (b1LB == 0 && b1UB != 1))
{
secondNeedsSetCode = true;
}
if ( b2UB == -1 || (b2LB == 0 && b2UB != 1))
{
firstNeedsSetCode = true;
}
// if all bounds OK, set boolean to true
if (firstLowerBoundOK && secondLowerBoundOK && firstUpperBoundOK && secondUpperBoundOK)
{
boundsOK = true;
}
if ( subclassOK && namesOK && boundsOK )
{
// then it's a redefinition
// so B is a parent
aFirstEnd.setSuperClassName(bFirstEnd.getClassName());
aSecondEnd.setSuperClassName(bSecondEnd.getClassName());
// Refer to the Association Specialization manual page --
// There are 2 cases for specialized code:
// 1- either the class will need code from it's superclass
// 2- or it will need code from it's own class
// 1- the case of A -- B and Aa -- B where Aa isA A, Aa will need super code
// 2- in the same case, B will need common code.
// *- int he A -- B, Aa -- Bb case, where Bb isA B, they both need super code.
// Checks class names for information relevant to the above.
if (aFirstEnd.getClassName().equals(bFirstEnd.getClassName()))
{
// i.e. same name as parent, so common code is required.
aSecondEnd.setNeedsCommonCode(true);
UmpleClass commonClass = model.getUmpleClass(aFirstEnd.getClassName());
AssociationVariable specializedAv = commonClass.getAssociationVariable(aSecondEnd.getClassName(), aSecondEnd.getRoleName());
AssociationVariable fromAv = commonClass.getAssociationVariable(bSecondEnd.getClassName(), bSecondEnd.getRoleName());
specializedAv.setSpecializedFromVariable(fromAv);
}
else
{
aSecondEnd.setNeedsSuperCode(true);
}
// need to check both ends...
if (aSecondEnd.getClassName().equals(bSecondEnd.getClassName()))
{
aFirstEnd.setNeedsCommonCode(true);
UmpleClass commonClass = model.getUmpleClass(aSecondEnd.getClassName());
AssociationVariable specializedAv = commonClass.getAssociationVariable(aFirstEnd.getClassName(), aFirstEnd.getRoleName());
AssociationVariable fromAv = commonClass.getAssociationVariable(bFirstEnd.getClassName(), bFirstEnd.getRoleName());
specializedAv.setSpecializedFromVariable(fromAv);
}
else
{
aFirstEnd.setNeedsSuperCode(true);
}
// -- probably legacy, comment out and see if it breaks.
if (bFirstEnd.getClassName().equals(aFirstEnd.getClassName()))
{
A.setCommonClassName(bFirstEnd.getClassName());
}
else if (bSecondEnd.getClassName().equals(aSecondEnd.getClassName()))
{
A.setCommonClassName(bSecondEnd.getClassName());
}
aFirstEnd.setMulChangedToOne(firstMulChToOne);
aSecondEnd.setMulChangedToOne(secondMulChToOne);
aFirstEnd.setMulChangedToN(firstMulChToN);
aSecondEnd.setMulChangedToN(secondMulChToN);
aFirstEnd.setReqSetCode(firstNeedsSetCode);
aSecondEnd.setReqSetCode(secondNeedsSetCode);
B.setIsSpecialized(true);
B.addSpecializedAssociation(A);
A.setIsSpecialization(true);
A.setSpecializedFrom(B);
}
}
visited.add(A);
}
}
// Issue #961. Ensure that all superclasses and subclasses do not have associations to two different
// classes that use the same role name, and is not a specialization.
private void checkDuplicateAssociationNamesClassHierarchy()
{
for (UmpleClass uClass : model.getUmpleClasses())
{
List<String> visitedSupers = new ArrayList<String>();
UmpleClass superClass = uClass.getExtendsClass();
while (superClass != null)
{
// Prevent cycles.
if (visitedSupers.contains(superClass.getName()))
{
break;
}
visitedSupers.add(superClass.getName());
ArrayList<String> duplicateNames = new ArrayList<String>();
for(Association assoc : uClass.getAssociations())
{
AssociationEnd assocEnd = getDifferingEnd(assoc, uClass.getName());
ArrayList<String> existingNames = new ArrayList<String>();
String roleName = assocEnd.getRoleName();
// Check only associations that have a user-defined role name, and is not a specialization.
if (!assocEnd.getIsDefaultRoleName() && !assoc.getIsSpecialization() && !duplicateNames.contains(roleName))
{
for (Association superAssoc : superClass.getAssociations())
{
AssociationEnd superAssocEnd = getDifferingEnd(superAssoc, superClass.getName());
String superAssocRoleName = superAssocEnd.getRoleName();
if (superAssocEnd != null && superAssocRoleName.equals(roleName) &&
!existingNames.contains(superAssocRoleName) && !superAssocEnd.getClassName().equals(assocEnd.getClassName()))
{
getParseResult().addErrorMessage(new ErrorMessage(180,assoc.getTokenPosition(),
uClass.getName(),superClass.getName(),roleName));
duplicateNames.add(roleName);
}
existingNames.add(superAssocRoleName);
}
}
}
superClass = superClass.getExtendsClass();
}
}
}
// Get the end of an association opposite to the given class. If the association is reflexive,
// return the end with a user defined role name.
private AssociationEnd getDifferingEnd(Association assoc, String className)
{
AssociationEnd firstEnd = assoc.getEnd(0);
AssociationEnd secondEnd = assoc.getEnd(1);
Boolean checkFirstEnd = !firstEnd.getClassName().equals(className);
Boolean checkSecondEnd = !secondEnd.getClassName().equals(className);
if (!checkFirstEnd && !checkSecondEnd)
{
// Association is reflexive. Both ends are the same, but we want the one with a user defined role name.
if (!firstEnd.getIsDefaultRoleName())
{
return firstEnd;
}
return secondEnd;
}
if (checkFirstEnd)
{
return firstEnd;
}
return secondEnd;
}
private void checkDuplicateAssociationNames()
{
for(UmpleClass C : model.getUmpleClasses())
{
// Create the list of attribute names (for issue 272)
List<String> existingAttributeNames = new ArrayList<String>();
for (Attribute attr : C.getAttributes())
{
existingAttributeNames.add(attr.getName());
}
Boolean roleMatchesClassName, hasMultipleAssocToSameClass, isSymmetricReflexive;
List<String> classesWithAssociationsToCurrClass = new ArrayList<String>();
List<String> roleNameSameAsClassName = new ArrayList<String>();
List<String> existingNames = new ArrayList<String>();
List<Association> visitedAssociations = new ArrayList<Association>();
for(Association assoc : C.getAssociations())
{
roleMatchesClassName = false;
hasMultipleAssocToSameClass = false;
if (visitedAssociations.contains(assoc))
{
continue;
}
AssociationEnd firstEnd = assoc.getEnd(0);
AssociationEnd secondEnd = assoc.getEnd(1);
if ( assoc.getIsSpecialization() )
{
//getParseResult().addErrorMessage(new ErrorMessage(400,assoc.getTokenPosition(),C.getName(),firstEnd.getRoleName()));
continue;
}
isSymmetricReflexive = firstEnd.getModifier().equals("symmetricreflexive") || secondEnd.getModifier().equals("symmetricreflexive");
Boolean checkFirstEnd = !firstEnd.getClassName().equals(C.getName());
Boolean checkSecondEnd = !secondEnd.getClassName().equals(C.getName());
Boolean associationIsReflexive = !checkFirstEnd && !checkSecondEnd;
//issue 288: firstEnd of association does not indicate current (this) class being analyzed.
//If association is NOT reflexive, must check the differing class. Check if role name
//matches class name, but only if it is a user entered role name. Current class must
//also have multiple associations to the same class to cause java compile errors.
if(!associationIsReflexive && C.numberOfAssociations() > 1)
{
//check the differing class
if(checkFirstEnd)
{
if(roleNameSameAsClassName.contains(firstEnd.getClassName().toLowerCase()))
{
hasMultipleAssocToSameClass = true; //flag error 19
}
//is a user-defined role name and rolename matches class name
else if(firstEnd.getRoleName().toLowerCase().equals(firstEnd.getClassName().toLowerCase()) && !firstEnd.getIsDefaultRoleName())
{
roleNameSameAsClassName.add(firstEnd.getRoleName().toLowerCase());
}
classesWithAssociationsToCurrClass.add(firstEnd.getClassName());
}
//check the differing class
if(checkSecondEnd)
{
if(roleNameSameAsClassName.contains(secondEnd.getClassName().toLowerCase()))
{
hasMultipleAssocToSameClass = true; //flag error 19
}
//is a user-defined role name and rolename matches class name
else if(secondEnd.getRoleName().toLowerCase().equals(secondEnd.getClassName().toLowerCase()) && !secondEnd.getIsDefaultRoleName())
{
roleNameSameAsClassName.add(secondEnd.getRoleName().toLowerCase());
}
classesWithAssociationsToCurrClass.add(secondEnd.getClassName());
}
}
// check names on other-class end of associations to other classes
if (!isSymmetricReflexive && (checkFirstEnd || associationIsReflexive) && assoc.getIsLeftNavigable())
{
if (existingNames.contains(firstEnd.getRoleName()) || hasMultipleAssocToSameClass)
{
getParseResult().addErrorMessage(new ErrorMessage(19,assoc.getTokenPosition(),firstEnd.getClassName(), C.getName()));
return;
}
else if (existingAttributeNames.contains(firstEnd.getRoleName()))
{ // Check if the association name is the same as an attribute name
getParseResult().addErrorMessage(new ErrorMessage(23,assoc.getTokenPosition(),C.getName(),firstEnd.getRoleName()));
return;
}
else
{
existingNames.add(firstEnd.getRoleName());
}
}
if (!isSymmetricReflexive && (checkSecondEnd || associationIsReflexive) && assoc.getIsRightNavigable())
{
if (existingNames.contains(secondEnd.getRoleName()) || hasMultipleAssocToSameClass)
{
getParseResult().addErrorMessage(new ErrorMessage(19,assoc.getTokenPosition(), secondEnd.getClassName(), C.getName()));
return;
}
else if (existingAttributeNames.contains(secondEnd.getRoleName()))
{ // Check if the association name is the same as an attribute name
getParseResult().addErrorMessage(new ErrorMessage(23,assoc.getTokenPosition(),C.getName(),secondEnd.getRoleName()));
return;
}
else
{
existingNames.add(secondEnd.getRoleName());
}
}
// Special case for symmetric reflexive associations. Since only one end of the association is given
// a name, that one only needs to be checked, and the other needs to be ignored.
if(isSymmetricReflexive)
{
String roleNameToCheck = firstEnd.getRoleName();
if(roleNameToCheck.equals("") || roleNameToCheck == null)
{
roleNameToCheck = secondEnd.getRoleName();
}
if(existingNames.contains(roleNameToCheck))
{
getParseResult().addErrorMessage(new ErrorMessage(19,assoc.getTokenPosition(),C.getName(),roleNameToCheck));
return;
}
else if(existingAttributeNames.contains(roleNameToCheck))
{
getParseResult().addErrorMessage(new ErrorMessage(23,assoc.getTokenPosition(),C.getName(),roleNameToCheck));
return;
}
else
{
existingNames.add(roleNameToCheck);
}
}
if (associationIsReflexive)
{
// The UmpleClass is only expected to have duplicate references to reflexive associations
visitedAssociations.add(assoc);
}
}
}
}
private void checkSingletonAssociations()
{
for (Association association : model.getAssociations())
{
if (associationIsBetweenClassAndInterface (association)){continue;}
if (associationIsBetweenClassAndTrait(association)){continue;}
AssociationEnd myEnd = association.getEnd(0);
AssociationEnd yourEnd = association.getEnd(1);
if(getModel().getUmpleTraitTypeParameter(myEnd.getClassName()) || getModel().getUmpleTrait(myEnd.getClassName())!=null) return ;
if(getModel().getUmpleTraitTypeParameter(yourEnd.getClassName()) || getModel().getUmpleTrait(yourEnd.getClassName())!=null) return ;
UmpleClass myClass = model.getUmpleClass(myEnd.getClassName());
UmpleClass yourClass = model.getUmpleClass(yourEnd.getClassName());
if (myClass.getIsSingleton() && (yourEnd.getMultiplicity().getRangeParts()[0].equals("1") && yourEnd.getMultiplicity().getRangeParts()[1].equals("1")))
{
yourEnd.getMultiplicity().setRange("0", "1");
yourEnd.getMultiplicity().setBound(null);
setFailedPosition(association.getTokenPosition(), 2, association.getName());
}
if (yourClass.getIsSingleton() && (myEnd.getMultiplicity().getRangeParts()[0].equals("1") && myEnd.getMultiplicity().getRangeParts()[1].equals("1")))
{
myEnd.getMultiplicity().setRange("0", "1");
myEnd.getMultiplicity().setBound(null);
setFailedPosition(association.getTokenPosition(), 2, association.getName());
}
if(myClass.getIsSingleton() && (myEnd.getMultiplicity().getUpperBound() < 0 || myEnd.getMultiplicity().getUpperBound() > 1))
{
setFailedPosition(association.getTokenPosition(), 10, myEnd.getClassName());
}
if(yourClass.getIsSingleton() && (yourEnd.getMultiplicity().getUpperBound() < 0 || yourEnd.getMultiplicity().getUpperBound() > 1))
{
setFailedPosition(association.getTokenPosition(), 10, yourEnd.getClassName());
}
}
}
private void addUnlinkedAssociations()
{
for (Association association : unlinkedAssociations)
{
if (associationIsBetweenClassAndInterface (association)){continue;}
if (associationIsBetweenClassAndTrait(association)){continue;}
AssociationEnd myEnd = association.getEnd(0);
AssociationEnd yourEnd = association.getEnd(1);
UmpleClass myClass = model.getUmpleClass(myEnd.getClassName());
UmpleClass yourClass = model.getUmpleClass(yourEnd.getClassName());
AssociationVariable myAs = new AssociationVariable(myEnd.getRoleName(),myEnd.getClassName(),myEnd.getModifier(),null,myEnd.getMultiplicity(),association.getIsLeftNavigable());
myAs.setIsComposition(association.getIsLeftComposition());
AssociationVariable yourAs = new AssociationVariable(yourEnd.getRoleName(),yourEnd.getClassName(),yourEnd.getModifier(),null,yourEnd.getMultiplicity(),association.getIsRightNavigable());
yourAs.setIsComposition(association.getIsRightComposition());
myAs.setRelevantEnd(0);
yourAs.setRelevantEnd(1);
myAs.setIsSpecialized(association.getIsSpecialized());
yourAs.setIsSpecialized(association.getIsSpecialized());
myAs.setIsSpecialization(association.getIsSpecialization());
yourAs.setIsSpecialization(association.getIsSpecialization());
myAs.setSuperClassName(myEnd.getSuperClassName());
yourAs.setSuperClassName(yourEnd.getSuperClassName());
myAs.setNeedsCommonCode(myEnd.getNeedsCommonCode());
myAs.setNeedsSuperCode(myEnd.getNeedsSuperCode());
yourAs.setNeedsCommonCode(yourEnd.getNeedsCommonCode());
yourAs.setNeedsSuperCode(yourEnd.getNeedsSuperCode());
myAs.setRelatedAssociation(yourAs);
if(!"".equals(myEnd.getPriority())) { myAs.setPriority(myEnd.getPriority()); }
if(!"".equals(yourEnd.getPriority())) { yourAs.setPriority(yourEnd.getPriority()); }
if (association.isImmutable())
{
boolean set = myAs.setImmutable();
if (set) { yourAs.setImmutable(); }
else
{
setFailedPosition(association.getTokenPosition(),17);
}
}
boolean added = myClass.addAssociationVariable(yourAs);
if (!added)
{
if (myClass.isImmutable()) { setFailedPosition(association.getTokenPosition(),17); }
else { setFailedPosition(association.getTokenPosition(),13); }
return;
}
myClass.addAssociation(association);
added = yourClass.addAssociationVariable(myAs);
if (!added)
{
if (myClass == yourClass) { setFailedPosition(association.getTokenPosition(),18); }
else { setFailedPosition(association.getTokenPosition(),13); }
return;
}
yourClass.addAssociation(association);
if (myAs.getIsNavigable())
{
yourClass.addReferencedPackage(myClass.getPackageName());
}
if (yourAs.getIsNavigable())
{
myClass.addReferencedPackage(yourClass.getPackageName());
}
}
}
private void checkSortedAssociations()
{
for (Association association : model.getAssociations())
{
if (associationIsBetweenClassAndInterface (association)){continue;}
AssociationEnd myEnd = association.getEnd(0);
AssociationEnd yourEnd = association.getEnd(1);
UmpleClass myClass = model.getUmpleClass(myEnd.getClassName());
UmpleClass yourClass = model.getUmpleClass(yourEnd.getClassName());
String value;
if(!"".equals(yourEnd.getPriority())){
Attribute temp = yourClass.getAttribute(yourEnd.getPriority());
if(temp != null)
{
if(Pattern.matches("Integer|Short|Long|Float|Double|String", temp.getType()))
{
String attributeType = yourClass.getName();
String priorityType = temp.getType();
String sortedName = yourEnd.getPriority().substring(0,1).toUpperCase() + yourEnd.getPriority().substring(1);
String php_codeblock =
"\n function($x, $y)\n"+
" {\n"+
" return $x->get"+ sortedName +"() -\n"+
" $y->get"+ sortedName +"();\n"+
" }";
String java_codeblock =
"\n new Comparator<" + attributeType +">(){\n"+
" @Override\n"+
" public int compare("+attributeType+" arg0, "+attributeType+" arg1)\n"+
" {\n"+
" return (("+priorityType+")arg0.get"+sortedName+"()).compareTo(\n"+
" (("+priorityType+")arg1.get"+sortedName+"()));\n"+
" }\n"+
" }";
Attribute priority = new Attribute(yourEnd.getRoleName()+"Priority","Comparator<" + attributeType +">", "", "", false, yourClass);
CodeBlock cb = new CodeBlock();
cb.setCode("Php", php_codeblock);
cb.setCode("Java", java_codeblock);
cb.setCode("Ruby", "\"\"");
priority.setCodeblock(cb);
myClass.addAttribute(priority);
}
else
setFailedPosition(association.getTokenPosition(), 24, yourEnd.getPriority(), myClass.getName());
}
else
setFailedPosition(association.getTokenPosition(), 25, yourClass.getName(), yourEnd.getPriority());
}
if(!"".equals(myEnd.getPriority())){
Attribute temp = myClass.getAttribute(myEnd.getPriority());
if(temp != null)
{
if(Pattern.matches("Integer|Short|Long|Float|Double|String", temp.getType()))
{
String attributeType = myClass.getName() ;
String priorityType = temp.getType();
String sortedName = myEnd.getPriority().substring(0,1).toUpperCase() + myEnd.getPriority().substring(1);
String php_codeblock =
"\n function($x, $y)\n"+
" {\n"+
" return $x->get"+ sortedName +"() -\n"+
" $y->get"+ sortedName +"();\n"+
" }";
String java_codeblock =
"\n new Comparator<" + attributeType +">(){\n"+
" @Override\n"+
" public int compare("+attributeType+" arg0, "+attributeType +" arg1)\n"+
" {\n"+
" return (("+priorityType+")arg0.get"+sortedName+"()).compareTo(\n"+
" (("+priorityType+")arg1.get"+sortedName+"()));\n"+
" }\n"+
" }";
Attribute priority = new Attribute(myEnd.getRoleName()+"Priority","Comparator<" + attributeType +">", "", "", false, myClass);
CodeBlock cb = new CodeBlock();
cb.setCode("Php", php_codeblock);
cb.setCode("Java", java_codeblock);
cb.setCode("Ruby", "\"\"");
priority.setCodeblock(cb);
yourClass.addAttribute(priority);
}
else
setFailedPosition(association.getTokenPosition(), 24, myEnd.getPriority(), yourClass.getName());
}
else
setFailedPosition(association.getTokenPosition(), 25, myClass.getName(), myEnd.getPriority());
}
}
}
private void checkClassInterfaceAssocations(Association a){
if (associationIsBetweenClassAndInterface(a)){
boolean hasCorrectArrow = !a.getIsLeftNavigable()&&a.getIsRightNavigable(); // Assocation has "->" arrow
if (!hasCorrectArrow){
setFailedPosition(a.getTokenPosition(), 20, a.getEnd(0).getClassName());
}
}
}
private void checkAbstractClassAssociations(Association a) {
boolean isBetweenClassAndAbstractClass = associationIsBetweenClassAndAbstractClass(a);
boolean isBetweenAbstractClassAndClass = associationIsBetweenAbstractClassAndClass(a);
if(isBetweenClassAndAbstractClass || isBetweenAbstractClassAndClass) {
boolean hasCorrectArrow = false;
if(isBetweenClassAndAbstractClass)
hasCorrectArrow = !a.getIsLeftNavigable()&&a.getIsRightNavigable(); //association has "->" arrow
if(isBetweenAbstractClassAndClass)
hasCorrectArrow = a.getIsLeftNavigable()&&!a.getIsRightNavigable(); //association has "<-" arrow
Multiplicity endAMultiplicity = a.getEnd(0).getMultiplicity();
Multiplicity endBMultiplicity = a.getEnd(1).getMultiplicity();
boolean lowerBoundGreaterThanZero = endAMultiplicity.getLowerBound() > 0 && endBMultiplicity.getLowerBound() > 0;
if (!hasCorrectArrow && lowerBoundGreaterThanZero) {
setFailedPosition(a.getTokenPosition(), 80, a.getEnd(0).getClassName());
}
}
}
private void checkAssociationsValidity() {
for (Association a : model.getAssociations()){
checkClassInterfaceAssocations(a);
checkAbstractClassAssociations(a);
}
}
/*
* Analyzes a token flagged to be a method in which case the data that makes up the method will be populated into a
* method instance and added to an Umple element (which could be an Umple class).
*
* @param method The token flagged to be the method.
* @param uElement The Umple element for which the method will be added.
*/
private void analyzeMethod(Token method, UmpleElement uElement) {
analyzeMethod(method, uElement, false);
}
/*
* Analyzes a token flagged to be a method in which case the data that makes up the method will be populated into a
* method instance and added to an Umple element (which could be an Umple class).
*
* Also returns the updated Method entity.
*
* @param method The token flagged to be the method.
* @param uElement The Umple element for which the method will be added.
* @param isStatefulMethod True if the method is stateful and should not be added to the class element.
* @return The updated Method entity.
*/
private Method analyzeMethod(Token method, UmpleElement uElement, boolean isStatefulMethod) {
String modifier = "";
Method aMethod = new Method("","","",false);
// Set method position
aMethod.setPosition(method.getPosition());
aMethod.setEndPosition(method.getEndPosition());
// Add comments above the method to the method.
for (Comment c : lastComments)
{
aMethod.addComment(c);
}
// Go through all the sub tokens of the "method token" to obtain details about it, using them to populate a method instance.
List<String> langs = new ArrayList<String>();
CodeBlock cb = new CodeBlock("");
String iValue = null;
Position iPosition = null;
MethodBody meth = new MethodBody(cb);
//559b
Map<String,Position> implementationPositions = new HashMap<String,Position>();
List<String> langsImplementation = new ArrayList<String>();
boolean canClear = false;
for(Token token : method.getSubTokens())
{
if (token.is("modifier"))
{
// TODO: Why the extra space? Means we have to remove later
modifier += " " + (token.getValue());
aMethod.setModifier(modifier);
}
else if(token.is("abstract")) {
boolean wasSet = aMethod.setIsAbstract(true);
// Ensure the value was set.
if (wasSet == false)
{
setFailedPosition(token.getPosition(), 0, "Unable to make method abstract!");
}
}
else if(token.is("methodThrowsExceptions")){
for(Token exceptionToken:token.getSubTokens())
{
if(exceptionToken.is("exception"))
{
aMethod.addException(exceptionToken.getValue());
}
}
}
else if(token.is("static"))
{
modifier += " static ";
aMethod.setModifier(modifier);
}
else if (token.is("type"))
{
if (aMethod.getIsQueued() && !token.getValue().equals("void"))
{
getParseResult().addErrorMessage(new ErrorMessage(41,token.getPosition()));
}
else
{
aMethod.setType(token.getValue());
}
}
else if (token.is("queued"))
{
aMethod.setIsQueued(true);
}
else if (token.is("list"))
{
aMethod.setType(aMethod.getType()+"[]");
}
else if (token.is("methodDeclarator"))
{
analyzeMethodDeclarator(token, uElement, aMethod);
}
else if (token.is("code"))
{
if(langs.isEmpty())
{
if( (cb.getCode("") != null) && (!cb.getCode("").equals("")) && (!cb.getCode("").equals("\n")))
getParseResult().addErrorMessage(new ErrorMessage(49,aMethod.getPosition(),
aMethod.getName(),aMethod.getClass().getName(), implementationPositions.get("").getLineNumber()+"", implementationPositions.get("").getFilename(),
token.getPosition().getLineNumber()+"", token.getPosition().getFilename(), ""));
else
{
cb.setCode(token.getValue());
aMethod.setCodePosition(token.getPosition());
//559b
implementationPositions.put("", token.getPosition());
langsImplementation.add("");
}
}
else
{
for(String str: langs)
{
if( (cb.getCode(str) != null) && (!cb.getCode(str).equals("")) && (!cb.getCode(str).equals("\n")))
getParseResult().addErrorMessage(new ErrorMessage(49,aMethod.getPosition(),
aMethod.getName(),aMethod.getClass().getName(), implementationPositions.get(str).getLineNumber()+"", implementationPositions.get(str).getFilename(),
token.getPosition().getLineNumber()+"", token.getPosition().getFilename(), str));
else
{
implementationPositions.put(iValue, iPosition);
cb.setCode(str,/*(cb.getCode(str)!=null?cb.getCode(str)+"\n":"")+*/ token.getValue());
if("Java".equals(str))
{
aMethod.setCodePosition(token.getPosition());
}
}
}
//559b
langs.clear();
}
canClear = true;
}
else if (token.is("codeLang"))
{
if(canClear)
{
langs.clear();
canClear = false;
}
langs.add(token.getValue());
//559b
langsImplementation.add(token.getValue());
iPosition = token.getPosition();
iValue = token.getValue();
//implementationPositions.put(token.getValue(), token.getPosition());
}
else if (token.is("precondition")){
if (uElement instanceof UmpleClass){
analyzePrecondition(token, (UmpleClass) uElement, aMethod);
}
}
else if (token.is("postcondition")){
if (uElement instanceof UmpleClass){
analyzePostcondition(token, (UmpleClass) uElement, aMethod);
}
}
else if (token.is("assertion")){
if (uElement instanceof UmpleClass){
analyzeMethodAssertion(token, (UmpleClass) uElement,meth, aMethod);
}
}
}
meth.setCodeblock(cb);
meth.setImplementationPositions(implementationPositions);
aMethod.setMethodBody(meth);
// Handle Class/Interface Modifications
if (uElement instanceof UmpleClass)
{
UmpleClass uClass = (UmpleClass) uElement;
// Add Getter/Setter/Constructor to Class
boolean shouldAddMethod = !isStatefulMethod && !isConstructorOrGetSet(uClass, aMethod, false);
//Issue 559
//if(uClass.hasMethod(aMethod) && !uClass.getMethod(aMethod).getIsConstructor())
if(uClass.hasMethod(aMethod))
{
Method actualMethod = uClass.getMethod(aMethod);
List<String> implementations = new ArrayList<String>();
for(String l : langsImplementation)
if(actualMethod.getExistsInLanguage(l))
implementations.add(l);
else
{
if(!l.equals(""))
actualMethod.getMethodBody().setExtraCode(l, aMethod.getMethodBody().getExtraCode(l));
else
actualMethod.getMethodBody().setExtraCode("", aMethod.getMethodBody().getExtraCode(l));
actualMethod.getMethodBody().getImplementationPositions().put(l, aMethod.getMethodBody().getImplementationPositions().get(l));
}
if(!implementations.isEmpty())
{
for(String l : implementations)
{
if(!isGetterSetter(actualMethod) && (actualMethod.getMethodBody().getExtraCode(l).equals("") ||
actualMethod.getMethodBody().getExtraCode(l).equals("\n"))&&
(!aMethod.getMethodBody().getExtraCode(l).equals("") &&
!aMethod.getMethodBody().getExtraCode(l).equals("\n")) &&
(!actualMethod.getMethodBody().getExtraCode(l).equals(aMethod.getMethodBody().getExtraCode(l))))
{
actualMethod.getMethodBody().setExtraCode(l, aMethod.getMethodBody().getExtraCode(l));
actualMethod.getMethodBody().getImplementationPositions().put(l, aMethod.getMethodBody().getImplementationPositions().get(l));
}
else
{
if (isGetterSetter(actualMethod)) {
String methodName = aMethod.getName();
String attributeName = methodName.substring(3,methodName.length()).toLowerCase();
getParseResult().addErrorMessage(new ErrorMessage(1009, aMethod.getPosition(), methodName, attributeName, uClass.getName()));
}
//Issue 771
else if(uClass.hasSameType(aMethod))
{
getParseResult().addErrorMessage(new ErrorMessage(49,aMethod.getPosition(),
aMethod.getName(),uClass.getName(), actualMethod.getMethodBody().getImplementationPositions().get(l).getLineNumber()+"", actualMethod.getMethodBody().getImplementationPositions().get(l).getFilename(),
aMethod.getMethodBody().getImplementationPositions().get(l).getLineNumber()+"", aMethod.getPosition().getFilename(), l));
}
else
{
getParseResult().addErrorMessage(new ErrorMessage(71,aMethod.getPosition(),
aMethod.getName(),uClass.getName(), actualMethod.getMethodBody().getImplementationPositions().get(l).getLineNumber()+"", actualMethod.getMethodBody().getImplementationPositions().get(l).getFilename(),
aMethod.getMethodBody().getImplementationPositions().get(l).getLineNumber()+"", aMethod.getPosition().getFilename(), l));
}
}
}
}
/*
if ((actualMethod.getMethodBody().getExtraCode().isEmpty()) &&
(!aMethod.getMethodBody().getExtraCode().isEmpty()))
{
actualMethod.setMethodBody(aMethod.getMethodBody());
actualMethod.setPosition(aMethod.getPosition());
}
else
{
getParseResult().addErrorMessage(new ErrorMessage(49,aMethod.getPosition(),
aMethod.getName(),uClass.getName(), actualMethod.getPosition().getLineNumber()+"", actualMethod.getPosition().getFilename(),
aMethod.getPosition().getLineNumber()+"", aMethod.getPosition().getFilename()));
}
*/
}
if ((!uClass.hasMethod(aMethod) || uClass.getMethod(aMethod).getIsConstructor()) && shouldAddMethod)
//if (!uClass.hasMethod(aMethod) && shouldAddMethod)
{
// See issue #1351. Convert regular methods to default bodies for state-dependent methods
// if they exist.
if (uClass.getStateDependentMethods().contains(aMethod) && !aMethod.isIsAbstract()) {
uClass.addStateDependentMethod(aMethod, null);
}
else
{
uClass.addMethod(aMethod);
}
}
// Make class abstract if an abstract method was added
if(uClass.hasMethod(aMethod) && aMethod.getIsAbstract())
{
uClass.setIsAbstract(true);
uClass.addUnimplementedMethod(aMethod);
}
// If method not added to class, add it to list of
// unimplemented methods
if(!uClass.hasMethod(aMethod))
{
//Issue 567 and 1351
if(shouldAddMethod && !uClass.getStateDependentMethods().contains(aMethod))
uClass.addUnimplementedMethod(aMethod);
}
}
else if (uElement instanceof UmpleTrait)
{
UmpleTrait uTrait = (UmpleTrait) uElement;
if (method.is("abstractMethodDeclaration")) aMethod.setIsAbstract(true);
// Add Getter/Setter/Constructor to Class
// boolean shouldAddMethod = isConstructorOrGetSet(uClass, aMethod);
if (!uTrait.hasMethod(aMethod)) // && shouldAddMethod)
{
String msg = "Please do not modify the following method.";
aMethod.addCommentAt(new Comment(msg),0);
msg = "The following method comes from trait "+uTrait.getName()+".";
aMethod.addCommentAt(new Comment(msg),1);
msg = "Trait "+uTrait.getName()+" has been used in classes: ";
aMethod.addCommentAt(new Comment(msg),2);
uTrait.addMethod(aMethod);
}
// Make class abstract if an abstract method was added
if(uTrait.hasMethod(aMethod) && aMethod.getIsAbstract())
{
uTrait.setIsAbstract(true);
uTrait.addUnimplementedMethod(aMethod);
}
// If method not added to class, add it to list of
// unimplemented methods
if(!uTrait.hasMethod(aMethod))
{
uTrait.addUnimplementedMethod(aMethod);
}
}
else if (uElement instanceof UmpleInterface)
{
UmpleInterface uInterface = (UmpleInterface) uElement;
if (!uInterface.hasMethod(aMethod))
{
uInterface.addMethod(aMethod);
}
}
return aMethod;
}
private void analyzeMethodAssertion (Token token, UmpleElement uElement,MethodBody meth, Method aMethod)
{
UmpleTestCase uTestCase = new UmpleTestCase(aMethod.getName(),(UmpleClass)uElement);
UmpleAssertion assertion = new UmpleAssertion("","","","","",uTestCase);
if (token.getValue("assertType").equals("assertTrue"))
{
assertion.setAssertCode(token.getValue("code"));
assertion.setType("true");
assertion.setLevel("method");
meth.addUmpleAssertion(assertion);
}
if (token.getValue("assertType").equals("assertFalse"))
{
assertion.setAssertCode(token.getValue("code"));
assertion.setType("false");
assertion.setLevel("method");
meth.addUmpleAssertion(assertion);
}
if (token.getValue("assertType").equals("assertEqual"))
{
assertion.setAssertCode(token.getValue("code"));
assertion.setType("equal");
assertion.setLevel("method");
meth.addUmpleAssertion(assertion);
}
}
/*
* Analyzes a method header, from a token flagged to be one, to populate a method instance for things such as the
* method name, type and parameters.
*
* @param token The token flagged to be a method header.
* @param aMethod The method to be populated from the analysis of the token.
*/
private void analyzeMethodDeclarator(Token token, UmpleElement uElement, Method aMethod)
{
// Go through all sub tokens of the method token to obtain data such as the methods name, parameters etc and add them to the method.
for(Token methodToken : token.getSubTokens())
{
if (methodToken.is("methodName"))
{
aMethod.setName(methodToken.getValue());
if(uElement instanceof UmpleClass) // to detect if user manually added a constructor
{
UmpleClass uClass = (UmpleClass)uElement;
if(uClass.getName().equals(methodToken.getValue()))
{
setFailedPosition(token.getPosition(), 170, "");
}
}
}
if (methodToken.is("parameterList"))
{
for(Token parameterToken : methodToken.getSubTokens())
{
boolean isList = false;
if (parameterToken.is("parameter"))
{
String paramType="";
if (parameterToken.getSubToken("type") != null)
{
paramType = parameterToken.getSubToken("type").getValue();
}
if (parameterToken.getSubToken("list") != null)
{
isList = parameterToken.getSubToken("list").getValue() != null;
}
String paramName = parameterToken.getSubToken("name").getValue();
MethodParameter aParameter = new MethodParameter(paramName,paramType,null,null, false);
aParameter.setIsList(isList);
aMethod.addMethodParameter(aParameter);
}
}
}
}
}
/*
* Analyzes a constant, from a token flagged to be one, to add a constant instance to an Umple element.
*
* @param constantToken The token flagged to be a constant.
* @param uElement The Umple element for which a new constant will be added (populated from analysis of the token).
*/
private void analyzeConstant(Token constantToken, UmpleElement uElement)
{
Constant aConstant = new Constant("","","","");
String modifier = "";
boolean validName = Token.isValidIdentifier(constantToken.getValue("name"));
boolean properName = Token.isValidIdentifier(constantToken.getValue("name"), "[A-Z]");
// Throws an error if the name of the constant is not valid
if(validName != true){
setFailedPosition(constantToken.getPosition(), 160, constantToken.getValue("name"));
return;
}
// Throws a warning if the name of the constant does not start with an uppercase
// as some languages may depend on constants being uppercase
if(properName != true){
setFailedPosition(constantToken.getPosition(), 161, constantToken.getValue("name"));
}
boolean isInternal = constantToken.getValue("internal") != null;
if(isInternal){
aConstant.setIsInternal(true);
}
else{
aConstant.setIsInternal(false);
}
// Create the Constant Object
for(Token token : constantToken.getSubTokens())
{
if (token.is("modifier"))
{
modifier += " " + (token.getSubToken(0).getName());
aConstant.setModifier(modifier);
}
else if (token.is("name"))
{
aConstant.setName(token.getValue());
}
else if (token.is("type"))
{
aConstant.setType(token.getValue());
}
else if (token.is("value"))
{
aConstant.setValue(token.getValue());
}
}
Boolean typeIsNull = ((constantToken.getValue("type") == null) || (constantToken.getValue("type").equals("")));
Boolean valueIsNull = ((constantToken.getValue("value") == null) || (constantToken.getValue("value").equals("")));
if((typeIsNull != true) && (valueIsNull != true))
{
if(!compareTypeToValue(constantToken.getValue("type"), constantToken.getValue("value")))
{
setFailedPosition(constantToken.getPosition(),141,constantToken.getValue("type"),constantToken.getValue("value"));
}
}
else if ((typeIsNull == true) && (valueIsNull != true))
{
aConstant.setType(inferType(constantToken.getValue("value"), constantToken.getValue("type")));
}
else if(typeIsNull == true)
{
aConstant.setType("String");
if(valueIsNull == true)
{
aConstant.setValue(null);
}
}
// Sets the default value of a constant that has not been initialized
if(aConstant.getValue() == null || aConstant.getValue() == "")
{
aConstant.setValue(defaultConstantValue(aConstant.getType(), constantToken));
}
// Checks to see if another constant shares the name of the current constant
for(Constant tmpConst : ((UmpleClassifier)uElement).getConstants()){
if (tmpConst.getName().equals(aConstant.getName())){
if (uElement instanceof UmpleClass)
{
setFailedPosition(constantToken.getPosition(), 22, uElement.getName(), aConstant.getName());
}
else if (uElement instanceof UmpleInterface)
{
setFailedPosition(constantToken.getPosition(), 112, uElement.getName(), aConstant.getName());
}
}
}
// Add constant to Class or Interface
if (uElement instanceof UmpleClass)
{
UmpleClass uClass = (UmpleClass) uElement;
uClass.addConstant(aConstant);
}
else if (uElement instanceof UmpleInterface)
{
UmpleInterface uInterface = (UmpleInterface) uElement;
uInterface.addConstant(aConstant);
}
}
private String defaultConstantValue(String type, Token attributeToken)
{
String value = "";
if(type.equals("Float") || type.equals("float"))
{
try{
String lang = this.getModel().getGenerate(0).getLanguage();
if(lang.equals("Java"))
{
//This call is made when Java is explicitly declared as the target language
value = "0.0f";
setFailedPosition(attributeToken.getPosition(), 35, attributeToken.getValue("name"), type, "0.0f");
}
else{
value = "0.0";
setFailedPosition(attributeToken.getPosition(), 35, attributeToken.getValue("name"), type, "0.0");
}
}catch(java.lang.IndexOutOfBoundsException e){
//This call is made when Java is defaulted as the target language
value = "0.0f";
setFailedPosition(attributeToken.getPosition(), 35, attributeToken.getValue("name"), type, "0.0f");
}
}
else if(type.equals("Double") || type.equals("double"))
{
value = "0.0";
setFailedPosition(attributeToken.getPosition(), 35, attributeToken.getValue("name"), type, "0.0");
}
else if(type.equals("int") || type.equals("Integer"))
{
value = "0";
setFailedPosition(attributeToken.getPosition(), 35, attributeToken.getValue("name"), type, "0");
}
else if(type.equals("boolean") || type.equals("Boolean"))
{
value = "false";
setFailedPosition(attributeToken.getPosition(), 35, attributeToken.getValue("name"), type, "false");
}
else if(type.equals("String"))
{
value = "\"\"";
setFailedPosition(attributeToken.getPosition(), 35, attributeToken.getValue("name"), type, "empty String (\"\")");
}
else if(type.equals("Time"))
{
value = "\"00:00:00\"";
setFailedPosition(attributeToken.getPosition(), 35, attributeToken.getValue("name"), type, "00:00:00");
}
else if(type.equals("Date"))
{
java.sql.Date currentDate = new java.sql.Date(System.currentTimeMillis());
value = "\"" + currentDate.toString() + "\"";
setFailedPosition(attributeToken.getPosition(), 35, attributeToken.getValue("name"), type, currentDate.toString());
}
else
{
setFailedPosition(attributeToken.getPosition(), 37, attributeToken.getValue("name"), type);
}
return value;
}
private String getOperationParameters(Token aToken)
{
StringBuilder operationParameters = new StringBuilder();
String delimeter = "";
for(Token subToken : aToken.getSubTokens())
{
if(subToken.is("parameterType"))
{
operationParameters.append(delimeter+subToken.getValue());
delimeter = ",";
}
else if(subToken.is("parameterTypes"))
{
return getOperationParameters(subToken);
}
else if(subToken.is("parameterListing")) {
return getOperationParameters(subToken);
}
}
if(aToken.is("injectionOperation")) {
return "...";
}
return operationParameters.toString();
}
private List<String> getOperationsParameters(Token aToken)
{
ArrayList<String> operationsParameters = new ArrayList<String>();
for(Token subToken : aToken.getSubTokens())
{
if(subToken.is("injectionOperation"))
{
operationsParameters.add(getOperationParameters(subToken));
}
}
return operationsParameters;
}
private String getOperationSource(Token aToken)
{
StringBuilder operationSource = new StringBuilder();
String comma = "";
for(Token subToken : aToken.getSubTokens())
{
if(subToken.is("operationSource"))
{
operationSource.append(comma+subToken.getValue());
}
}
return operationSource.toString();
}
private String getOperationName(Token aToken)
{
StringBuilder operationName = new StringBuilder();
String comma = "";
for(Token subToken : aToken.getSubTokens())
{
if(subToken.is("operationName"))
{
operationName.append(comma+subToken.getValue());
comma = ",";
}
else if(subToken.is("injectionOperation"))
{
operationName.append(comma+getOperationName(subToken));
comma = ",";
}
}
return operationName.toString();
}
//TODO I changed the parameter's type. please remove this comment;
private void analyzeInjectionCode(Token injectToken, UmpleClassifier uClassifier)
{
String type = injectToken.is("beforeCode") ? "before" : "after";
CodeBlock cb = new CodeBlock();
String operationName = getOperationName(injectToken);
String operationSource = getOperationSource(injectToken).toLowerCase();
if(operationSource.equals("")) {
operationSource = "all";
}
CodeInjection injection = new CodeInjection(type,operationName,"",uClassifier);
injection.setOperationSource(operationSource);
List<String> operationsParameters = getOperationsParameters(injectToken);
for(String operationParameters : operationsParameters) {
injection.addParameter(operationParameters);
}
makeCodeInject(injectToken,injection,cb,uClassifier);
injection.setSnippet(cb);
if (uClassifier instanceof UmpleClass) {
((UmpleClass)uClassifier).addCodeInjection(injection);
if(!unanalyzedInjectionTokens.containsKey(uClassifier))
{
unanalyzedInjectionTokens.put(uClassifier, new ArrayList<Token>());
}
List<Token> currentTokens = unanalyzedInjectionTokens.get(uClassifier);
currentTokens.add(injectToken);
} else if (uClassifier instanceof UmpleTrait){
((UmpleTrait)uClassifier).addCodeInjection(injection);
}
}
private boolean checkParameterMatch(List<MethodParameter> parameters, String currentParameters) {
// Check if method parameters match injection parameters
boolean isParameterMatch = true;
if("...".equals(currentParameters) || (parameters.size() == 0 && "".equals(currentParameters))) {
isParameterMatch = true;
}
else if(parameters.size() != currentParameters.split(",").length)
{
isParameterMatch = false;
}
else {
int indx = 0;
for(String parameterType : currentParameters.split(",")) {
if(!parameterType.equals(parameters.get(indx).getType())) {
isParameterMatch = false;
}
indx++;
}
}
return isParameterMatch;
}
ArrayList<String> getAllMethodNames(UmpleClass uClass) {
ArrayList<String> methodNames = new ArrayList<String>();
ArrayList<String> todoExtendedClasses = new ArrayList<String>();
ArrayList<String> todoInheritedInterfaces = new ArrayList<String>();
todoExtendedClasses.add(uClass.getName());
while(!todoExtendedClasses.isEmpty())
{
String className = todoExtendedClasses.get(0);
todoExtendedClasses.remove(className);
UmpleClass eClass = model.getUmpleClass(className);
if(eClass == null)
{
todoInheritedInterfaces.add(className);
continue;
}
todoExtendedClasses.remove(className);
if(unlinkedExtends.get(eClass) != null)
{
todoExtendedClasses.addAll(unlinkedExtends.get(eClass));
}
methodNames.addAll(eClass.getMethodNames());
}
while(!todoInheritedInterfaces.isEmpty())
{
String interfaceName = todoInheritedInterfaces.get(0);
todoInheritedInterfaces.remove(interfaceName);
UmpleInterface eInterface = model.getUmpleInterface(interfaceName);
if(eInterface == null)
{
continue;
}
if(unlinkedInterfaceExtends.get(eInterface) != null)
{
todoInheritedInterfaces.addAll(unlinkedInterfaceExtends.get(eInterface));
}
for(Method m : eInterface.getMethods())
{
methodNames.add(m.getName());
}
}
return methodNames;
}
private void checkCodeInjectionValidity(Token injectToken, UmpleClass uClass, String operationSource) {
List<Method> methods = uClass.getMethods();
ArrayList<String> methodNames = getAllMethodNames(uClass);
String[] allOperations = getOperationName(injectToken).split(",");
List<String> allParameters = getOperationsParameters(injectToken);
for(int opInd = 0; opInd < allOperations.length; opInd++)
{
String operation = allOperations[opInd];
String currentParameters = allParameters.get(opInd);
Boolean hasExclusion = false;
if(operation.charAt(0) == '!' && "generated".equals(operationSource)) {
hasExclusion = true;
operation = operation.substring(1);
}
Boolean matches = false;
// check against generated methods
if ("all".equals(operationSource) || "generated".equals(operationSource)) {
for(String methodName : methodNames)
{
if (uClass.matchOperationMethod(operation, methodName)) {
matches = true;
}
}
// issue #1351 - check against state-dependent methods
final String operationFinal = operation;
if (!matches && uClass.getStateDependentMethods().stream().anyMatch(
m -> m.getName().equals(operationFinal)))
{
matches = true;
}
}
// check against custom methods
if("all".equals(operationSource) || "custom".equals(operationSource)){
for(Method method : methods)
{
boolean tmpMatches = uClass.matchOperationMethod(operation, method.getName()) && checkParameterMatch(method.getMethodParameters(), currentParameters);
matches = matches || tmpMatches;
}
}
if(!matches) {
if(hasExclusion && "generated".equals(operationSource)) {
getParseResult().addErrorMessage(new ErrorMessage(1014, injectToken.getPosition(), operation));
}
else {
getParseResult().addErrorMessage(new ErrorMessage(1012, injectToken.getPosition(), operation));
}
}
if(!"...".equals(currentParameters) && !"custom".equals(operationSource)) {
getParseResult().addErrorMessage(new ErrorMessage(1013, injectToken.getPosition(), operationSource));
}
}
}
//TODO I changed the parameter's type. please remove this comment;
private void makeCodeInject(Token injectToken,CodeInjection injection, CodeBlock cb, UmpleClassifier uClassifier)
{
List<String> langs = new ArrayList<String>();
for(Token sub: injectToken.getSubTokens())
{
if(sub.is("codeLang"))
{
langs.add(sub.getValue());
}
if(sub.is("code"))
{
if(langs.size()==0)
{
cb.setCode(sub.getValue());
injection.setCodePosition(sub.getPosition());
}
else
{
for(String lang:langs)
{
cb.setCode(lang,sub.getValue());
if("Java".equals(lang))
{
injection.setCodePosition(sub.getPosition());
}
}
}
langs.clear();
}
if(sub.is("codeInject"))
{
makeCodeInject(sub,injection, cb , uClassifier);
}
}
injection.setPosition(injectToken.getPosition());
}
private void addKey (Token keysToken, UmpleClass aClass, Map <UmpleClass, List<Token>> unlinkedKeysTokens)
{
// If the class already has a key defined, set fail position
if (unlinkedKeysTokens.containsKey(aClass))
{
setFailedPosition(keysToken.getPosition(), 7, keysToken.getParentToken().getValue("name"));
}
if (keysToken.is("defaultKey"))
{
aClass.getKey().setIsDefault(true);
setFailedPosition(keysToken.getPosition(), 47, aClass.getName());
return;
}
List<Token> keyTokenList = new ArrayList<Token>();
List<String> tokensAdded = new ArrayList<String>();
// Go through all sub-tokens of the key to add them to the list of keys for this class
for (Token token : keysToken.getSubTokens())
{
if (token.is("keyId"))
{
//Checks for duplicate keys
if(tokensAdded.contains(token.getValue()))
{
setFailedPosition(keysToken.getPosition(), 26, token.getValue(), aClass.getName());
}
else
{
keyTokenList.add(token);
tokensAdded.add(token.getValue());
}
}
}
unlinkedKeysTokens.put(aClass, keyTokenList);
}
private void analyzeSymmetricReflexiveAssociation(Token symmetricReflexiveAssociationToken, UmpleClass aClass)
{
String myName = symmetricReflexiveAssociationToken.getValue("roleName");
String myType = aClass.getName();
String myModifier = "symmetricreflexive";
String myBound = symmetricReflexiveAssociationToken.getValue("bound");
String myLowerBound = symmetricReflexiveAssociationToken.getValue("lowerBound");
String myUpperBound = symmetricReflexiveAssociationToken.getValue("upperBound");
Multiplicity myMult = new Multiplicity();
myMult.setBound(myBound);
myMult.setRange(myLowerBound,myUpperBound);
//String myIsSpecialized = symmetricReflexiveAssociationToken.getValue("isSpecialized");
//String myIsSpecialization = symmetricReflexiveAssociationToken.getValue("isSpecialization");
AssociationVariable myAs = new AssociationVariable(myName,myType,myModifier,null,myMult,true);
AssociationVariable yourAs = new AssociationVariable(myName,myType,myModifier,null,myMult,true);
//myAs.setIsSpecialized(myIsSpecialized.equals("true"));
//myAs.setIsSpecialization(myIsSpecialization.equals("true"));
//yourAs.setIsSpecialized(myIsSpecialized.equals("true"));
//yourAs.setIsSpecialization(myIsSpecialization.equals("true"));
myAs.setRelatedAssociation(yourAs);
aClass.addAssociationVariable(yourAs);
AssociationEnd leftEnd = new AssociationEnd(null,myType,myModifier,myType,myMult);
AssociationEnd rightEnd = new AssociationEnd(myName,myType,myModifier,myType,myMult);
Association assoc = new Association(true, true, false, false, leftEnd, rightEnd);
assoc.setTokenPosition(symmetricReflexiveAssociationToken.getPosition());
assoc.setTokenEndPosition(symmetricReflexiveAssociationToken.getEndPosition());
aClass.addAssociation(assoc);
model.addAssociation(assoc);
}
private Association createAssociation(String navigation, AssociationEnd firstEnd, AssociationEnd secondEnd)
{
Association association;
if(navigation != null){
boolean isNavigable = "--".equals(navigation) || "<@>-".equals(navigation) || "-<@>".equals(navigation);
boolean isFirstNavigable = "<-".equals(navigation) || isNavigable;
boolean isSecondNavigable = "->".equals(navigation) || isNavigable;
boolean isLeftComposition = "<@>-".equals(navigation);
boolean isRightComposition = "-<@>".equals(navigation);
association = new Association(isFirstNavigable,isSecondNavigable,isLeftComposition,isRightComposition,firstEnd,secondEnd);
}else{
association = new Association(true, true, false, false, firstEnd, secondEnd);
}
return association;
}
private Association analyzeAssociation(Token associationToken, String defaultMyType)
{
Token parentToken = associationToken.getParentToken();
Token gParentToken = parentToken.getParentToken();
boolean isAssociationClass = parentToken.is("associationClassDefinition") || gParentToken.is("associationClassDefinition");
boolean isSingleAssociationEnd = associationToken.is("singleAssociationEnd");
boolean isInlineAssociation = associationToken.is("inlineAssociation");
Token myEndToken = null;
Token yourEndToken = null;
String navigation = null;
String associationModifier = null;
String name = null;
//Issue 213/131: associations can be inside AssociationClasses, but
//inline associations inside associationClasses are treated just like in regular classes
if(isAssociationClass && !isInlineAssociation){
if(isSingleAssociationEnd){
myEndToken = parentToken;
associationModifier = associationToken.getValue("modifier");
navigation = null;
name = parentToken.getValue("name");
}else{ //association inside associationClass
myEndToken = parentToken.getParentToken();
associationModifier = parentToken.getValue("modifier");
navigation = parentToken.getValue("arrow");
name = gParentToken.getValue("name");
}
yourEndToken = associationToken;
}else{//inline association or external "association{..}" block
name = parentToken.getValue("name");
int myMultOffset = 0;
int yourMultOffset = 2;
Token associationModifierToken = associationToken.getSubToken("modifier");
if (associationModifierToken != null){
associationModifier = associationModifierToken.getValue();
myMultOffset++;
yourMultOffset++;
}
myEndToken = associationToken.getSubToken(myMultOffset);
navigation = associationToken.getValue("arrow");
yourEndToken = associationToken.getSubToken(yourMultOffset);
}
AssociationEnd firstEnd = createPreliminaryAssociationEnd(myEndToken, defaultMyType);
AssociationEnd secondEnd = createPreliminaryAssociationEnd(yourEndToken, defaultMyType);
if(firstEnd == null || secondEnd == null){
setFailedPosition(associationToken.getPosition(), 8, name);
return null;
}
if(isAssociationClass && isSingleAssociationEnd){
String roleName = associationToken.getValue("otherEndroleName");
if(roleName != null){
firstEnd.setRoleName(roleName);
}
}
String myType = firstEnd.getClassName();
String myRoleName = firstEnd.getRoleName();
String yourType = secondEnd.getClassName();
String yourRoleName = secondEnd.getRoleName();
Multiplicity myMult = firstEnd.getMultiplicity();
Multiplicity yourMult = secondEnd.getMultiplicity();
if("".equals(firstEnd.getClassName())){
firstEnd.setClassName(defaultMyType);
}
firstEnd.setReferenceToClassName(yourType);
secondEnd.setReferenceToClassName(myType);
//Association Classes have Mutiplicities switched between ends: an association
//between A and B in associationClass C becomes A -- C and B -- C
if(isAssociationClass){
firstEnd.setMultiplicity(secondEnd.getMultiplicity());
secondEnd.setMultiplicity(myMult);
}
updateAssociationEnds(firstEnd,secondEnd);
// Trap cases where both ends are the same class (reflexive) and
// there is no or same role name and same multiplicity. Fixes issue 295
if(myType.equals(yourType) && ((myRoleName == null && yourRoleName == null) || (myRoleName != null && yourRoleName != null && myRoleName.equals(yourRoleName))) && myMult.toString().equals(yourMult.toString())) {
setFailedPosition(associationToken.getPosition(), 21, myType);
return null;
}
if (firstEnd.getRoleName().equals(secondEnd.getRoleName()) && firstEnd.getClassName().equals(secondEnd.getClassName())){
setFailedPosition(associationToken.getPosition(), 32, firstEnd.getRoleName());
return null;
}
Association association = createAssociation(navigation,firstEnd,secondEnd);
if (isAssociationClass)
{
AssociationClass assocClass;
assocClass = (AssociationClass) model.getUmpleClass(gParentToken.getValue("name"));
if (assocClass == null) { assocClass = (AssociationClass) model.getUmpleClass(parentToken.getValue("name")); }
if (assocClass != null) { assocClass.addAssociatedTo(association); }
}
if (associationModifier != null && "immutable".equals(associationModifier)){
association.setImmutable();
}
association.setTokenPosition(associationToken.getPosition());
association.setTokenEndPosition(associationToken.getEndPosition());
if (!association.isValid()){
Token atFaultToken = association.whoIsInvalid() == 0 ? myEndToken : yourEndToken;
String invalidBound = atFaultToken.getValue("bound") == null ? invalidBound = atFaultToken.getValue("lowerBound") + ".." + atFaultToken.getValue("upperBound") : atFaultToken.getValue("bound");
setFailedPosition(atFaultToken.getPosition(), 9, invalidBound);
return null;
}
model.addAssociation(association);
if(!isInlineAssociation){ unlinkedAssociations.add(association); }
//check for non-constraint side warning per issue347
if(isInlineAssociation || !isAssociationClass){
analyzeNonconstraintAssociationEndForWarning(associationToken, firstEnd, secondEnd);
}
return association;
}
/*
* Given a [[associationEnd]], [[singleAssociationEnd]], [[inlineAssociationEnd]] or
* a [[associationClassDefinition]] token, creates a
* preliminary AssociationEnd object to help in the creation of an Association object.
* The resulting object will have to be completed with setReferenceToClassName()depending on the type of association.
* @return an AssociationEnd object or null if an error occured
*/
private AssociationEnd createPreliminaryAssociationEnd(Token associationEndToken, String defaultType){
if(associationEndToken != null){
String name, type, modifier, roleName, bound, lowerBound, upperBound, priority;
bound = lowerBound = upperBound = priority = roleName = null;
Multiplicity mult = new Multiplicity();
String typeIndex;
//special case when [[singleAssociationEnd]] is used in one end: the
//parent associationClassDefinition is used as the other
if(associationEndToken.is("associationClassDefinition")){
name = null;
type = associationEndToken.getValue("name");
modifier = null;
mult.setRange("1","1");
typeIndex = "name";
}else{
type = associationEndToken.getValue("type");
modifier = associationEndToken.getValue("modifier");
roleName = associationEndToken.getValue("roleName");
bound = associationEndToken.getValue("bound");
lowerBound = associationEndToken.getValue("lowerBound");
upperBound = associationEndToken.getValue("upperBound");
priority = associationEndToken.getValue("priority");
mult.setRange(lowerBound, upperBound);
mult.setBound(bound);
typeIndex = "type";
}
// Report an error if the multiplicity is invalid
if (!mult.isValid()){
String invalidBound = bound == null ? lowerBound + ".." + upperBound : bound;
setFailedPosition(associationEndToken.getPosition(), 4, invalidBound);
return null;
}
if(type == null){
type = defaultType;
}
AssociationEnd assocEnd = new AssociationEnd(roleName,type,modifier,null,mult);
if(priority != null){
assocEnd.setPriority(priority);
}
positionToClassNameReference.put(associationEndToken.getPosition(typeIndex),type);
return assocEnd;
}else{
return null;
}
}
private void updateAssociationEnds(AssociationEnd firstEnd, AssociationEnd secondEnd)
{
if (firstEnd.getRoleName().length() == 0)
{
String rawName = StringFormatter.toCamelCase(firstEnd.getClassName());
String name = firstEnd.getMultiplicity().isMany() ? model.getGlossary().getPlural(rawName) : rawName;
firstEnd.setRoleName(name);
firstEnd.setIsDefaultRoleName(true);
}
if (secondEnd.getRoleName().length() == 0)
{
String rawName = StringFormatter.toCamelCase(secondEnd.getClassName());
String name = secondEnd.getMultiplicity().isMany() ? model.getGlossary().getPlural(rawName) : rawName;
secondEnd.setRoleName(name);
secondEnd.setIsDefaultRoleName(true);
}
}
/*
* Analyzes a token flagged to be an association within an Umple class to create an instance of one and add it to the class.
*
* @param inlineAssociationToken The token flagged to be an inline association.
* @param aClass The Umple class for which an association instance will be added (populated from analysis of the token).
*/
private void analyzeinlineAssociation(Token inlineAssociationToken, UmpleClass aClass)
{
Association association = analyzeAssociation(inlineAssociationToken,aClass.getName());
if (!getParseResult().getWasSuccess())
{
return;
}
AssociationEnd myEnd = association.getEnd(0);
AssociationEnd yourEnd = association.getEnd(1);
AssociationVariable myAs = new AssociationVariable(myEnd.getRoleName(),myEnd.getClassName(),myEnd.getModifier(),null,myEnd.getMultiplicity(),association.getIsLeftNavigable());
myAs.setIsComposition(association.getIsLeftComposition());
AssociationVariable yourAs = new AssociationVariable(yourEnd.getRoleName(),yourEnd.getClassName(),yourEnd.getModifier(),null,yourEnd.getMultiplicity(),association.getIsRightNavigable());
yourAs.setIsComposition(association.getIsRightComposition());
myAs.setRelatedAssociation(yourAs);
if(!"".equals(myEnd.getPriority())) { myAs.setPriority(myEnd.getPriority()); }
if(!"".equals(yourEnd.getPriority())) { yourAs.setPriority(yourEnd.getPriority()); }
if (association.isImmutable())
{
boolean set = myAs.setImmutable();
if (set)
{
yourAs.setImmutable();
}
else
{
setFailedPosition(inlineAssociationToken.getPosition(),17);
}
}
// Add comments above the association to the association.
for (Comment c : lastComments)
{
yourAs.addComment(c);
}
// set last association made to be yourAs. clear lastattr.
lastassoc = yourAs;
lastassocPosition = inlineAssociationToken.getPosition();
lastattr = null;
boolean added = aClass.addAssociationVariable(yourAs);
if (added)
{
unlinkedAssociationVariables.add(yourAs);
aClass.addAssociation(association);
}
else
{
if (aClass.isImmutable()) { setFailedPosition(inlineAssociationToken.getPosition(),17); }
else { setFailedPosition(inlineAssociationToken.getPosition(),13); }
}
}
/*
* Set warning if non-constraint side of association is not many(*) or optional(0..1) association type per issue 347
*
* @param Token The token is either inlineAssociationToken or associationDefinationToken.
* @param AssociationEnd the first associationEnd.
* @param AssociationEnd the second associationEnd.
*/
private void analyzeNonconstraintAssociationEndForWarning(Token associationToken, AssociationEnd myEnd, AssociationEnd yourEnd)
{
AssociationEnd nonConstraintEnd = null;
boolean isUnidirection = true;
//manual check as getIsLeftNavigatable() applies for "--" as well
Token arrowToken = associationToken.getSubToken("arrow");
boolean isLeftArrow = arrowToken.getValue().equals("<-"); // || arrowToken.getValue().equals("<@>-");
boolean isRightArrow = arrowToken.getValue().equals("->"); // || arrowToken.getValue().equals("-<@>");
//determine the non-arrow side
if(isLeftArrow)
{
nonConstraintEnd = yourEnd;
}
else if(isRightArrow)
{
nonConstraintEnd = myEnd;
}
else
{
isUnidirection = false;
}
if(isUnidirection)
{
String nonConstraintEndLower = nonConstraintEnd.getLowerBoundString();
String nonConstraintEndUpper = nonConstraintEnd.getUpperBoundString();
//only allow optional and many constraint on non-arrow side
boolean isMany = (nonConstraintEndLower.equals("0")) && (nonConstraintEndUpper.equals("*"));
boolean isOptional = (nonConstraintEndLower.equals("0")) && (nonConstraintEndUpper.equals("1"));
if(!isMany && !isOptional)
{
String bound = "";
//case of mandatory constraint
if(nonConstraintEndLower.equals(nonConstraintEndUpper))
bound = nonConstraintEndLower;
else
bound = "[" +nonConstraintEndLower+", "+nonConstraintEndUpper+"]";
setFailedPosition(associationToken.getPosition(), 36, bound);
}
}
}
/*
* Analyzes a token flagged to be an attribute within an Umple class to create an instance of one and add it to the class.
*
* @param attributeToken The token flagged to be an attribute.
* @param aClass The Umple class for which an attribute instance will be added (populated from analysis of the token).
*/
private void analyzeAttribute(Token attributeToken, UmpleClass aClass)
{
boolean isAutounique = attributeToken.getValue("autounique") != null;
boolean isUnique = attributeToken.getValue("unique") != null;
boolean isLazy = attributeToken.getValue("lazy") != null;
boolean isIvar = attributeToken.getValue("ivar") != null;
boolean isConstant = "const".equals(attributeToken.getValue("modifier"));
boolean validName = Token.isValidIdentifier(attributeToken.getValue("name"));
boolean properName = !Token.isValidIdentifier(attributeToken.getValue("name"), "[A-Z]");
boolean looksLikeAssociation = attributeToken.getValue("name").contains("--") || attributeToken.getValue("name").contains("->");
looksLikeAssociation = looksLikeAssociation || attributeToken.getValue("name").contains("<-") || attributeToken.getValue("name").contains("..");
looksLikeAssociation = looksLikeAssociation || attributeToken.getValue("name").contains("*");
looksLikeAssociation = looksLikeAssociation || attributeToken.getValue("name").contains("<@>-") || attributeToken.getValue("name").contains("-<@>");
String modifier = attributeToken.getValue("modifier");
String type = attributeToken.getValue("type");
String name = attributeToken.getValue("name");
String value = attributeToken.getValue("value");
String derivedValue = attributeToken.getValue("code");
if(isAutounique || attributeToken.getValue("modifier") != null){
if(attributeAutouniqueImmutable == null)
attributeAutouniqueImmutable = new HashMap<Token, UmpleClass>();
attributeAutouniqueImmutable.put(attributeToken, aClass);
}
if(!validName)
{
if(looksLikeAssociation){
setFailedPosition(attributeToken.getPosition(), 132, name);
}
else if(isConstant){
setFailedPosition(attributeToken.getPosition(), 160, name);
}
else {
setFailedPosition(attributeToken.getPosition(), 130, name);
}
return;
}
if(!properName && !isConstant){
setFailedPosition(attributeToken.getPosition(), 131, name);
}
else if(properName && isConstant){
setFailedPosition(attributeToken.getPosition(), 161, name);
}
//allow singleton with constant and predefined variables
if (aClass.getIsSingleton() && !isConstant && !isLazy && (value == null))
{
isLazy = true;
setFailedPosition(attributeToken.getPosition(), 1, name);
}
// check to see if type has angle brackets <>
if (type != null)
{
int lang_pos = type.lastIndexOf('<');
int rang_pos = type.lastIndexOf('>');
if (lang_pos > 0 && rang_pos > 0 && lang_pos < rang_pos)
{
setFailedPosition(attributeToken.getPosition(), 46, name, aClass.getName(), type);
}
}
if (isLazyRedundant(isLazy, value))
{
setFailedPosition(attributeToken.getPosition(), 3, aClass.getName(), name);
}
for(Attribute aAttribute : aClass.getAttributes()){
if (aAttribute.getName().equals(name)){
setFailedPosition(attributeToken.getPosition(), 22, aClass.getName(), name);
}
}
CodeBlock languageSpecificCode = new CodeBlock();
if (derivedValue != null)
{
value = "";
List<String> codelangs = new ArrayList<String>();
for(Token tkn: attributeToken.getSubTokens())
{
if(tkn.is("codeLang"))
{
codelangs.add(tkn.getValue());
} else if(tkn.is("code")) {
if(codelangs.isEmpty())
{
languageSpecificCode.setCode(tkn.getValue());
} else {
for(String lang: codelangs)
{
languageSpecificCode.setCode(lang, tkn.getValue());
}
codelangs.clear();
}
}
}
}
if ("defaulted".equals(modifier) && value == null)
{
setFailedPosition(attributeToken.getPosition(), 6, attributeToken.getValue("name"));
return;
}
if (isUnique)
{
UniqueIdentifier uniqueIdentifier = new UniqueIdentifier(name,type,modifier,value);
aClass.setUniqueIdentifier(uniqueIdentifier);
}
if (isAutounique)
{
type = "Integer";
}
if(type != null)
{
Matcher m = Pattern.compile("([a-zA-Z_][0-9a-zA-Z_]*(<(.*)>)?)").matcher(type);
if(!m.matches())
{
setFailedPosition(attributeToken.getPosition(), 140, type);
return;
}
}
if(type != null)
{
if("public".equals(type) || "private".equals(type) || "protected".equals(type))
{
setFailedPosition(attributeToken.getPosition(), 142, name, type);
}
}
boolean isList = attributeToken.getValue("list") != null;
if(type != null && value != null)
{
if (!isList) { // will check list initializer later
if(!compareTypeToValue(type,value))
{
setFailedPosition(attributeToken.getPosition(),141,type,value);
}
}
}
else if (type == null && value != null)
{
type = inferType(value, type);
}
else if(type == null)
{
type = "String";
}
if(attributeToken.getValue("modifier") != null)
{
if((attributeToken.getValue("modifier").contains("const")) && (attributeToken.getValue("value") == null))
{
value = defaultConstantValue(type, attributeToken);
}
}
Attribute attribute = new Attribute(name,type,modifier,value,isAutounique,aClass);
attribute.setIsUnique(isUnique);
attribute.setIsLazy(isLazy);
attribute.setIsIvar(isIvar);
if (name == null)
{
String rawName = StringFormatter.toCamelCase(type);
name = isList ? model.getGlossary().getPlural(rawName) : rawName;
}