diff --git a/src/main/java/ai/AIStateMachinesAndAttributes.java b/src/main/java/ai/AIStateMachinesAndAttributes.java new file mode 100644 index 0000000000000000000000000000000000000000..ddbba13976eb4f3ec7b593a913fa6eb8900d7ae6 --- /dev/null +++ b/src/main/java/ai/AIStateMachinesAndAttributes.java @@ -0,0 +1,268 @@ +/* Copyright or (C) or Copr. GET / ENST, Telecom-Paris, Ludovic Apvrille + * + * ludovic.apvrille AT enst.fr + * + * This software is a computer program whose purpose is to allow the + * edition of TURTLE analysis, design and deployment diagrams, to + * allow the generation of RT-LOTOS or Java code from this diagram, + * and at last to allow the analysis of formal validation traces + * obtained from external tools, e.g. RTL from LAAS-CNRS and CADP + * from INRIA Rhone-Alpes. + * + * This software is governed by the CeCILL license under French law and + * abiding by the rules of distribution of free software. You can use, + * modify and/ or redistribute the software under the terms of the CeCILL + * license as circulated by CEA, CNRS and INRIA at the following URL + * "http://www.cecill.info". + * + * As a counterpart to the access to the source code and rights to copy, + * modify and redistribute granted by the license, users are provided only + * with a limited warranty and the software's author, the holder of the + * economic rights, and the successive licensors have only limited + * liability. + * + * In this respect, the user's attention is drawn to the risks associated + * with loading, using, modifying and/or developing or reproducing the + * software by the user in light of its specific status of free software, + * that may mean that it is complicated to manipulate, and that also + * therefore means that it is reserved for developers and experienced + * professionals having in-depth computer knowledge. Users are therefore + * encouraged to load and test the software's suitability as regards their + * requirements in conditions enabling the security of their systems and/or + * data to be ensured and, more generally, to use and operate it in the + * same conditions as regards security. + * + * The fact that you are presently reading this means that you have had + * knowledge of the CeCILL license and that you accept its terms. + */ + + +package ai; + + +import avatartranslator.AvatarBlock; +import avatartranslator.AvatarSpecification; +import avatartranslator.tosysmlv2.AVATAR2SysMLV2; +import myutil.TraceManager; +import org.apache.batik.anim.timing.Trace; + +import java.util.ArrayList; + +/** + * Class AIStateMachinesAndAttributes + * <p> + * Creation: 04/08/2023 + * + * @author Ludovic APVRILLE + * @version 1.0 04/08/2023 + */ + + +public class AIStateMachinesAndAttributes extends AIInteract implements AISysMLV2DiagramContent, AIAvatarSpecificationRequired { + private static String[] SUPPORTED_DIAGRAMS = {"BD"}; + private static String[] EXCLUSIONS_IN_INPUT = {"state", "method"}; + + public static String KNOWLEDGE_ON_JSON_FOR_STATE_MACHINES = "When you are asked to identify the SysML state machine of a block, " + + "return them as a JSON specification " + + "formatted as follows:" + + "{states: [{ \"name\": \"Name of state\", transitions [{ \"destinationstate\" : \"state name\", \"guard\": \"boolean condition\", " + + "\"after\": \"time " + + "value\", \"action\":" + + " \"attribute action or signal receiving/sending\"}]}]} ."; + + public static String ATTRIBUTES_JSON_FOR_STATE_MACHINES = "Now, give the type of all attributes (and not signals) you have just used using the " + + "following JSON " + + "format: {attributes: [{\"name\" : \"name of attribute\", \"type\": \"int or boolean\""; + + public static String[] CONSTRAINTS_ON_JSON_FOR_STATE_MACHINES = { + "# Respect: in actions, use only signals already defined in the block.", + "# Respect: at least one state must be called \"Start\", which is the start state.", + "# Respect: if a guard, an action, or an after is empty, use an empty string \"\", do not use \"null\"." , + "# Respect: an action contains either a variable affectation, e.g. \"x = x + 1\" or a signal send/receive. " , + "# Respect: if a transition contains several actions, use a \";\" to separate them. " , + "# Respect: a signal send or receive is signalName(..) with inside the right attributes or values. " , + "# Respect: the attribute of an action is named by its identifier, do not reference its block. " , + "# Respect: a state machine can use only the attribute of its block. " , + "# Respect: A guard cannot contain a reference to a signal. " , + "# Respect: To reference the attribute \"x\" of block \"B\", use \"x\" and never \"B.x\" nor \"B::x\"" }; + + + private AvatarSpecification specification; + + private String diagramContentInSysMLV2; + + private static String KNOWLEDGE_SYSTEM_SPECIFICATION = "The specification of the system is:"; + private static String KNOWLEDGE_SYSTEM_BLOCKS = "The specification of the blocks in SysML V2 is:"; + + private String[] QUESTION_IDENTIFY_STATE_MACHINE = {"From the system specification, and from the definition of blocks and" + + " their " + + "connections, identify the state machine of block: "}; + + + public AIStateMachinesAndAttributes(AIChatData _chatData) { + super(_chatData); + } + + public void internalRequest() { + + // Add the knowledge, retrieve the block names, attributes, etc. + + /*if (!chatData.knowledgeOnStateMachines) { + chatData.aiinterface.addKnowledge(KNOWLEDGE_ON_JSON_FOR_STATE_MACHINES, "ok"); + chatData.knowledgeOnStateMachines = true; + } + + chatData.aiinterface.addKnowledge(KNOWLEDGE_SYSTEM_SPECIFICATION + chatData.lastQuestion, "ok"); + chatData.aiinterface.addKnowledge(KNOWLEDGE_SYSTEM_BLOCKS + diagramContentInSysMLV2, "ok");*/ + + // Getting block names for SysMLV2 spec + //TraceManager.addDev("SysML V2 spec: " + diagramContentInSysMLV2); + //ArrayList<String> blockNames = AVATAR2SysMLV2.getAllBlockNames(diagramContentInSysMLV2); + ArrayList<String> blockNames = specification.getAllBlockNames(); + + TraceManager.addDev("Going to handle the following blocks: "); + for(String s: blockNames) { + TraceManager.addDev("\tblock: " + s); + } + + + boolean done = false; + int cpt = 0; + + String questionT; + + + for(String blockName: blockNames) { + TraceManager.addDev("Handling block: " + blockName); + done = false; cpt = 0; + int max = 3; + + + initKnowledge(); + + AvatarBlock b = specification.getBlockWithName(blockName); + questionT = QUESTION_IDENTIFY_STATE_MACHINE[0] + blockName; + if (b != null) { + questionT += ". This block has the following attributes: " + specification.getStringAttributes(b); + questionT += ". This block has the following signals: " + specification.getStringSignals(b); + } + while (!done && cpt < max) { + done = true; + + + boolean ok1 = makeQuestion(questionT); + if (!ok1) { + TraceManager.addDev("Make question #1 failed"); + } + String json1 = extractJSON(); + + //chatData.aiinterface.addKnowledge(questionT, chatData.lastAnswer); + questionT = ATTRIBUTES_JSON_FOR_STATE_MACHINES; + boolean ok2 = makeQuestion(questionT); + + if (!ok2) { + TraceManager.addDev("Make question #2 failed"); + } + String json2 = extractJSON(); + + if (ok1 && ok2 && specification != null) { + if (b != null) { + ArrayList<String> errors = new ArrayList<>(); + ArrayList<String> ret; + TraceManager.addDev("Adding attributes of the state machine of " + blockName); + ret = b.addAttributesFromJSON(json2); + if (ret != null) { + errors.addAll(ret); + } + TraceManager.addDev("Making the state machine of " + blockName); + + errors.addAll(b.makeStateMachineFromJSON(json1, true)); + + ret = b.addAttributesFromJSON(json2); + if (ret != null) { + errors.addAll(ret); + } + + if ((errors != null) && (errors.size() > 0)) { + done = false; + initKnowledge(); + + questionT += "Your specification was: " + json1 + ". But it is not correct because of the following errors:"; + + for (String s : errors) { + TraceManager.addDev("Error in JSON: " + s); + questionT += "\n- " + s; + } + questionT += "\nProvide only the updated state machine using only the already defined attributes"; + } else { + TraceManager.addDev("SMD done for Block " + blockName); + } + } else { + TraceManager.addDev("ERROR: no block named " + blockName); + } + } else { + TraceManager.addDev("Null specification or false ok"); + } + + waitIfConditionTrue(!done && cpt < max); + + cpt ++; + } + // Remove knowledge of previous questions + initKnowledge(); + /*while(cpt > 0) { + cpt --; + chatData.aiinterface.removePreviousKnowledge(); + }*/ + } + TraceManager.addDev("Reached end of AIStateMachine internal request cpt=" + cpt); + + } + + private void initKnowledge() { + chatData.aiinterface.clearKnowledge(); + + chatData.aiinterface.addKnowledge(KNOWLEDGE_SYSTEM_SPECIFICATION + chatData.lastQuestion, "ok"); + //chatData.aiinterface.addKnowledge(KNOWLEDGE_SYSTEM_BLOCKS + diagramContentInSysMLV2, "ok"); + chatData.aiinterface.addKnowledge(KNOWLEDGE_ON_JSON_FOR_STATE_MACHINES, "ok"); + + for(int i=0; i<CONSTRAINTS_ON_JSON_FOR_STATE_MACHINES.length; i++) { + chatData.aiinterface.addKnowledge(CONSTRAINTS_ON_JSON_FOR_STATE_MACHINES[i], "ok"); + } + } + + public Object applyAnswer(Object input) { + TraceManager.addDev("Apply answer in AIState Machine"); + if (specification == null) { + TraceManager.addDev("Null spec"); + } else { + TraceManager.addDev("Non null spec"); + } + if (input == null) { + return specification; + } + + return specification; + } + + public void setAvatarSpecification(AvatarSpecification _specification) { + specification = _specification; + }; + + public void setDiagramContentInSysMLV2(String _diagramContentInSysMLV2) { + diagramContentInSysMLV2 = _diagramContentInSysMLV2; + }; + + public String[] getValidDiagrams() { + return SUPPORTED_DIAGRAMS; + } + + public String[] getDiagramExclusions() { + return EXCLUSIONS_IN_INPUT; + } + + + + + +} diff --git a/src/main/java/avatartranslator/AvatarBlock.java b/src/main/java/avatartranslator/AvatarBlock.java index f1e60c871873281d6814929704477752aa2cf5ef..638b6447d77ffa97b9e4657852a0a0f64b2ae044 100644 --- a/src/main/java/avatartranslator/AvatarBlock.java +++ b/src/main/java/avatartranslator/AvatarBlock.java @@ -1183,13 +1183,15 @@ public class AvatarBlock extends AvatarElement implements AvatarStateMachineOwne try { for (String action : actions) { + TraceManager.addDev("Handling action:" + action); // Affectation? if (action.contains("=")) { + TraceManager.addDev("Handling affectation:" + action); int index = action.indexOf('='); String variableName = action.substring(0, index).trim(); AvatarAttribute aa = getAvatarAttributeWithName(variableName); if (aa == null) { - TraceManager.addDev("The following action is not valid: " + action + " because it contains an attribute " + + TraceManager.addDev("The following action is not valid: " + action + " because it contains an attribute " + variableName + " which is not declared in the block " + getName()); errors.add("The following action is not valid: " + action + " because it contains an attribute " + variableName + " which is not declared in the block " + getName()); @@ -1250,55 +1252,35 @@ public class AvatarBlock extends AvatarElement implements AvatarStateMachineOwne } // signal sending / receiving - else if (action.contains("::")) { + else if (isASignalAction(action.trim())) { TraceManager.addDev("Handing communication action: " + action); - int index = action.indexOf("::"); - boolean isIn = action.substring(0, index).trim().compareTo("in") == 0; - String signalSent = action.substring(index+2); - - if (signalSent.length() >0) { - int indexLPar = signalSent.indexOf("("); - String sigName = signalSent; - if (indexLPar > - 1) { - sigName = signalSent.substring(0, indexLPar).trim(); - } - AvatarSignal atas = getAvatarSignalWithName(sigName); - if ((atas == null) && (!forceIfIncorrectExpression)) { - TraceManager.addDev("No signal named \"" + action + "\" in block \"" + getName() + "\""); - errors.add("No signal named \"" + action + "\" in block \"" + getName() + "\"" ); - } else { - if (atas == null) { - // Adding signal to block - atas = new AvatarSignal(sigName, isIn?AvatarSignal.IN : AvatarSignal.OUT, null); - addSignal(atas); - } - AvatarActionOnSignal aaos = new AvatarActionOnSignal( - sigName + "_aaos", atas, null, asm.getOwner()); - // Chaining components - asm.addElement(aaos); - AvatarTransition atBis = - new AvatarTransition(this, "name" + "_from_" + sigName, getReferenceObject()); - asm.addElement(atBis); - - AvatarStateMachineElement asme = at.getNext(0); - at.removeAllNexts(); - at.addNext(aaos); - aaos.addNext(atBis); - atBis.addNext(asme); - at = atBis; - - - } + int index = action.indexOf("("); + String sigName = action.substring(0, index); + AvatarSignal as = getAvatarSignalWithName(sigName); + if (as == null) { + errors.add("No signal named \"" + sigName + "\" in block \"" + getName() + "\"" ); } else { - TraceManager.addDev("No signal provided in the following action: " + action + "."); - errors.add("No signal provided in the following action: " + action + "."); - } + AvatarActionOnSignal aaos = new AvatarActionOnSignal( + sigName + "_aaos", as, null, asm.getOwner()); + asm.addElement(aaos); + AvatarTransition atBis = + new AvatarTransition(this, "name" + "_from_" + sigName, getReferenceObject()); + asm.addElement(atBis); + + AvatarStateMachineElement asme = at.getNext(0); + at.removeAllNexts(); + at.addNext(aaos); + aaos.addNext(atBis); + atBis.addNext(asme); + at = atBis; + } } else { + TraceManager.addDev("Other action:" + action); if (forceIfIncorrectExpression) { at.addAction(action); } else { @@ -1327,6 +1309,67 @@ public class AvatarBlock extends AvatarElement implements AvatarStateMachineOwne TraceManager.addDev("******************** State Machine of block: " + getName() + ":" + getStateMachine().toString()); + return errors; + } + + public static boolean isASignalAction(String s) { + int index = s.indexOf('('); + if (index == -1) { + return false; + } + + String tmp = s.substring(0, index); + + return tmp.trim().matches("[A-Za-z_][A-Za-z0-9_]*"); + } + + public ArrayList<String> addAttributesFromJSON(String _jsonSpec) { + if (_jsonSpec == null) { + return null; + } + + ArrayList<String> errors = new ArrayList<>(); + JSONObject mainObject; + + try { + mainObject = new JSONObject(_jsonSpec); + + JSONArray statesJSON = mainObject.getJSONArray("attributes"); + + for (int i = 0; i < statesJSON.length(); i++) { + JSONObject state0 = statesJSON.getJSONObject(i); + String name = AvatarSpecification.removeSpaces(state0.getString("name")); + String type = AvatarSpecification.removeSpaces(state0.getString("type")); + + if (type == null) { + type = "int"; + } + + AvatarType at; + if (type.compareTo("boolean") == 0) { + at = AvatarType.BOOLEAN; + } else if (type.compareTo("int") == 0){ + at = AvatarType.INTEGER; + } else { + errors.add("The following type is not valid: " + type + " in attribute " + name); + at = AvatarType.INTEGER; + } + + AvatarAttribute aa = getAvatarAttributeWithName(name); + if (aa != null){ + if (aa.getType() != at) { + errors.add("Attribute " + name + " already has type: " + aa.getType().getStringType()); + } + } else { + aa = new AvatarAttribute(name, at, this, this.getReferenceObject()); + addAttribute(aa); + } + + } + } catch (org.json.JSONException e) { + errors.add("Invalid JSON: " + e.getMessage()); + } + return errors; } } diff --git a/src/main/java/avatartranslator/AvatarSpecification.java b/src/main/java/avatartranslator/AvatarSpecification.java index c785a3c3c8451d9201a4332f135b353c930cbe4b..db8c8855f0ccf8b192695d10c75ec7862be06970 100644 --- a/src/main/java/avatartranslator/AvatarSpecification.java +++ b/src/main/java/avatartranslator/AvatarSpecification.java @@ -825,8 +825,12 @@ public class AvatarSpecification extends AvatarElement implements IBSParamSpec { jsonErrors.add("The declaration of signal " + sigName + " is not valid for block " + blockOName); } - - AvatarSignal asD = AvatarSignal.isAValidSignalThenCreate("in " + sigName, blockD); + AvatarSignal asD; + if (blockO == blockD) { + asD = AvatarSignal.isAValidSignalThenCreate("in " + sigName + "_in", blockD); + } else { + asD = AvatarSignal.isAValidSignalThenCreate("in " + sigName, blockD); + } if (asD == null) { jsonErrors.add("The declaration of signal " + sigName + " is not valid for block " + blockDName); @@ -1941,4 +1945,38 @@ public class AvatarSpecification extends AvatarElement implements IBSParamSpec { return lne; } + public StringBuffer getStringAttributes(AvatarBlock _ab) { + StringBuffer sb = new StringBuffer(""); + + if (_ab == null) { + return sb; + } + + for(AvatarAttribute aa: _ab.getAttributes()) { + sb.append(aa.toString() + "\n"); + } + return sb; + } + + public StringBuffer getStringSignals(AvatarBlock _ab) { + StringBuffer sb = new StringBuffer(""); + + if (_ab == null) { + return sb; + } + + for(AvatarSignal as: _ab.getSignals()) { + sb.append(as.toString() + "\n"); + } + return sb; + } + + public ArrayList<String> getAllBlockNames() { + ArrayList<String> ret = new ArrayList<>(); + for(AvatarBlock block: blocks) { + ret.add(block.getName()); + } + return ret; + } + } diff --git a/src/main/java/ui/window/JFrameAI.java b/src/main/java/ui/window/JFrameAI.java index 26fc187d55fa2fb6196129410c8ad87b937b63b5..bd16e8e1d6c3c9a5919b49c4d06885dba7fc3127 100644 --- a/src/main/java/ui/window/JFrameAI.java +++ b/src/main/java/ui/window/JFrameAI.java @@ -51,6 +51,7 @@ import ui.avatarbd.AvatarBDPanel; import ui.avatarrd.AvatarRDPanel; import ui.avatarrd.AvatarRDRequirement; import ui.util.IconManager; + import javax.swing.*; import javax.swing.event.ChangeEvent; import javax.swing.event.ChangeListener; @@ -82,22 +83,27 @@ public class JFrameAI extends JFrame implements ActionListener { "Identify system blocks (knowledge type #2) - Provide a system specification", "Identify software blocks - Provide a system specification", "Identify state" + " machines - Select a block diagram. Additionally, you can provide a system specification", + "Identify state machines and attributes - Select a block diagram. Additionally, you can provide a system specification", "A(I)MULET - Select a block diagram first"}; private static String[] AIInteractClass = {"AIChat", "AIReqIdent", "AIReqClassification", "AIChat", "AIDesignPropertyIdentification", "AIBlock", - "AIBlockConnAttrib", "AISoftwareBlock", "AIStateMachine", "AIAmulet"}; + "AIBlockConnAttrib", "AISoftwareBlock", "AIStateMachine", "AIStateMachinesAndAttributes", "AIAmulet"}; private static String[] INFOS = {"Chat on any topic you like", "Identify requirements from the specification of a system", "Classify " + "requirements from a requirement diagram", "Identify use cases and actors from a system specification", "Identify the typical properties" + - " to be proven " + - "from a block" + - " diagram", "Identify the system " + + " to be proven " + + "from a block" + + " diagram", "Identify the system " + "blocks from a specification", "Identify the system " + "blocks from a specification (another kind of knowledge)", "Identify the software blocks from a specification", "Identify the state " + "machines from a " + "system " + - "specification and a block diagram", "Formalize mutations to be performed on a block diagram"}; + "specification and a block diagram", "Identify the state " + + "machines and attributes from a " + + "system " + + "specification and a block diagram", + "Formalize mutations to be performed on a block diagram"}; protected JComboBox<String> listOfPossibleActions; @@ -337,7 +343,7 @@ public class JFrameAI extends JFrame implements ActionListener { TraceManager.addDev("Selected aiinteract: " + selected.aiInteract.getClass()); - if (selected.aiInteract instanceof AIAvatarSpecificationRequired) { + if (selected.aiInteract instanceof AIAvatarSpecificationRequired) { TraceManager.addDev("****** AIAvatarSpecificationRequired identified *****"); TDiagramPanel tdp = mgui.getCurrentMainTDiagramPanel(); boolean found = false; @@ -360,14 +366,14 @@ public class JFrameAI extends JFrame implements ActionListener { } } - if (selected.aiInteract instanceof AISysMLV2DiagramContent) { + if (selected.aiInteract instanceof AISysMLV2DiagramContent) { TraceManager.addDev("****** AISysMLV2DiagramContent identified *****"); TDiagramPanel tdp = mgui.getCurrentTDiagramPanel(); - String[] validDiagrams = ((AISysMLV2DiagramContent)(selected.aiInteract)).getValidDiagrams(); + String[] validDiagrams = ((AISysMLV2DiagramContent) (selected.aiInteract)).getValidDiagrams(); String className = tdp.getClass().getName(); boolean found = false; - for(String s: validDiagrams) { + for (String s : validDiagrams) { if (className.contains(s)) { found = true; break; @@ -376,8 +382,8 @@ public class JFrameAI extends JFrame implements ActionListener { if (found) { TraceManager.addDev("The selected diagram is valid"); - String[] exclusions = ((AISysMLV2DiagramContent)(selected.aiInteract)).getDiagramExclusions(); - StringBuffer sb = tdp.toSysMLV2Text(exclusions); + String[] exclusions = ((AISysMLV2DiagramContent) (selected.aiInteract)).getDiagramExclusions(); + StringBuffer sb = tdp.toSysMLV2Text(exclusions); if (sb == null) { error("The syntax of the selected diagram is incorrect"); return; @@ -399,7 +405,7 @@ public class JFrameAI extends JFrame implements ActionListener { ChatData selectedChat = selectedChat(); if (selectedChat.lastAnswer == null || selectedChat.aiChatData == null) { error("No answer to apply"); - return ; + return; } //TraceManager.addDev("Class of answer: " + selectedChat.aiInteract.getClass().getName()); @@ -422,12 +428,12 @@ public class JFrameAI extends JFrame implements ActionListener { currentChatIndex = answerPane.getSelectedIndex(); - switch(currentChatIndex) { + switch (currentChatIndex) { case 0: if (selectedChat.aiInteract instanceof ai.AIBlock) { applyIdentifySystemBlocks(selectedChat.aiInteract.applyAnswer(null)); } else if (selectedChat.aiInteract instanceof ai.AIBlockConnAttrib) { - applyIdentifySystemBlocks(selectedChat.aiInteract.applyAnswer(null)); + applyIdentifySystemBlocks(selectedChat.aiInteract.applyAnswer(null)); } else if (selectedChat.aiInteract instanceof ai.AISoftwareBlock) { applyIdentifySystemBlocks(selectedChat.aiInteract.applyAnswer(null)); } else if (selectedChat.aiInteract instanceof ai.AIReqIdent) { @@ -439,13 +445,16 @@ public class JFrameAI extends JFrame implements ActionListener { } else if (selectedChat.aiInteract instanceof ai.AIStateMachine) { TraceManager.addDev("Applying state machines"); applyIdentifyStateMachines(selectedChat.aiInteract.applyAnswer(null)); + } else if (selectedChat.aiInteract instanceof ai.AIStateMachinesAndAttributes) { + TraceManager.addDev("Applying state machines and attributes"); + applyIdentifyStateMachines(selectedChat.aiInteract.applyAnswer(null)); } else if (selectedChat.aiInteract instanceof ai.AIAmulet) { applyMutations(); } break; - case 1: - applyRequirementIdentification(); - break; + case 1: + applyRequirementIdentification(); + break; case 2: applyRequirementClassification(); break; @@ -542,7 +551,7 @@ public class JFrameAI extends JFrame implements ActionListener { ChatData selected = selectedChat(); for (TGComponent tgc : rdpanel.getAllRequirements()) { AvatarRDRequirement req = (AvatarRDRequirement) tgc; - String kind = (String)(selected.aiInteract.applyAnswer(req.getValue())); + String kind = (String) (selected.aiInteract.applyAnswer(req.getValue())); if (kind != null) { String k = JDialogRequirement.getKindFromString(kind); if (k != null) { @@ -558,7 +567,7 @@ public class JFrameAI extends JFrame implements ActionListener { rdpanel.repaint(); } - private void applyMutations(){ + private void applyMutations() { //AvatarSpecification avspec = mgui.gtm.getAvatarSpecification(); TDiagramPanel tdp = mgui.getCurrentTDiagramPanel(); if (!(tdp instanceof AvatarBDPanel)) { @@ -575,13 +584,13 @@ public class JFrameAI extends JFrame implements ActionListener { AvatarSpecification avspec = mgui.gtm.getAvatarSpecification(); - if (avspec == null){ + if (avspec == null) { error("AVATAR specification not found: aborting.\n"); return; } inform("Applying mutations to the model, please wait\n"); - avspec = (AvatarSpecification) (selectedChat().aiInteract.applyAnswer( avspec )); + avspec = (AvatarSpecification) (selectedChat().aiInteract.applyAnswer(avspec)); if (avspec != null) { mgui.drawAvatarSpecification(avspec);