// Copyright Paphus Solutions, all rights reserved. package org.botlibre.thought.language; import java.io.StringWriter; import java.util.ArrayList; import java.util.Collection; import java.util.HashMap; import java.util.Iterator; import java.util.List; import java.util.Map; import java.util.logging.Level; import org.botlibre.Bot; import org.botlibre.api.knowledge.Network; import org.botlibre.api.knowledge.Relationship; import org.botlibre.api.knowledge.Vertex; import org.botlibre.knowledge.Bootstrap; import org.botlibre.knowledge.Primitive; import org.botlibre.self.SelfDecompiler; import org.botlibre.thought.SubconsciousThought; import org.botlibre.util.Utils; /** * Analyse input for gramatic rules. * Define a language state machine for input response pairs. */ public class Comprehension extends SubconsciousThought { /** Define the traversal path types for the language state machine. */ public enum PathType { /** Traverse words and meanings only. */ Meaning, /** Traverse words, meaning, or verb variables. */ Verb, /** Traverse classification variables. */ Classification } public static int MAX_IDENTIFIER = 100; public Comprehension() { this.delay = 1000; } @Override public void awake() { String enabled = this.bot.memory().getProperty("Comprehension.enabled"); if (enabled != null) { setEnabled(Boolean.valueOf(enabled)); } super.awake(); } /** * Migrate to new properties system. */ public void migrateProperties() { Network memory = getBot().memory().newMemory(); Vertex thought = memory.createVertex(getClass()); Vertex property = thought.getRelationship(Primitive.ENABLED); if (property != null) { setEnabled((Boolean)property.getData()); } // Remove old properties. thought.internalRemoveRelationships(Primitive.ENABLED); memory.save(); saveProperties(); } public void saveProperties() { Network memory = getBot().memory().newMemory(); memory.saveProperty("Comprehension.enabled", String.valueOf(isEnabled()), true); memory.save(); } /** * The input must be processed by language first to associate the question/response. */ @Override public boolean isConsciousProcessingRequired() { return true; } /** * If the input is a sentence process it for language rules and variable substitution. */ @Override public boolean processInput(Vertex input, Network network) { if (isStopped()) { return false; } Language language = getBot().mind().getThought(Language.class); Vertex speaker = input.getRelationship(Primitive.SPEAKER); boolean learn = language.shouldLearn(input, speaker); boolean correction = input.hasRelationship(Primitive.ASSOCIATED, Primitive.CORRECTION); if (correction) { if (!language.shouldCorrect(input, speaker)) { return false; } } else if (!learn) { return false; } Vertex sentence = input.mostConscious(Primitive.INPUT); if (sentence == null || (!sentence.instanceOf(Primitive.SENTENCE))) { return false; } log("Processing sentence", Bot.FINE, sentence); Vertex questionInput = input.mostConscious(Primitive.QUESTION); if (questionInput == null) { log("No question", Bot.FINE, sentence); return false; } Vertex mimic = questionInput.getRelationship(Primitive.MIMIC); // If it was a mimic the response must be changed to be in the context of self, // and the question must be the question that was mimicked. if (mimic != null) { questionInput = mimic; questionInput = questionInput.copy(); questionInput.internalRemoveRelationships(Primitive.SPEAKER); questionInput.internalRemoveRelationships(Primitive.TARGET); questionInput.addRelationship(Primitive.SPEAKER, Primitive.SELF); questionInput.addRelationship(Primitive.TARGET, speaker); } // If it was a correction the response must be changed to be in the context of self, // and the question must be the original question that was corrected. if (correction) { input = input.copy(); input.internalRemoveRelationships(Primitive.SPEAKER); input.internalRemoveRelationships(Primitive.TARGET); input.addRelationship(Primitive.SPEAKER, Primitive.SELF); input.addRelationship(Primitive.TARGET, speaker); } Vertex question = questionInput.mostConscious(Primitive.INPUT); Relationship relationship = question.getRelationship(Primitive.RESPONSE, sentence); if (relationship == null) { log("No response", Bot.FINE, question); return false; } if (relationship.getCorrectness() < 0.5) { log("Insufficient correctness", Bot.FINE, relationship); return false; } log("Processing question response", Bot.FINE, question, sentence); List<Vertex> states = network.createVertex(Language.class).orderedRelations(Primitive.STATE); if (states == null) { return false; } Vertex stateMachine = states.get(states.size() - 1); if (stateMachine == null) { log("Missing state machine", Bot.FINE, states); return false; } Vertex currentState = stateMachine; Map<Vertex, Vertex> variables = new HashMap<Vertex, Vertex>(); // Get first case that gets sentence from input. List<Vertex> instructions = stateMachine.orderedRelations(Primitive.DO); if (instructions != null) { for (Vertex instruction : instructions) { if (instruction.instanceOf(Primitive.CASE)) { Vertex variable = instruction.getRelationship(Primitive.CASE); if ((variable != null) && (questionInput.matches(variable, variables) == Boolean.TRUE)) { currentState = instruction.getRelationship(Primitive.GOTO); } } } } if (currentState == stateMachine) { log("State machine missing sentence case.", Bot.FINE, question); return false; } List<Vertex> words = question.orderedRelations(Primitive.WORD); if (words == null) { log("Question missing words", Bot.FINE, question); return false; } if (isStopped()) { return false; } processState(currentState, words, 0, "", PathType.Meaning, sentence, questionInput, input, variables, network); processState(currentState, words, 0, "", PathType.Classification, sentence, questionInput, input, variables, network); processState(currentState, words, 0, "", PathType.Verb, sentence, questionInput, input, variables, network); if (isStopped()) { return false; } Vertex sourceCode = stateMachine.getRelationship(Primitive.SOURCECODE); if (sourceCode != null) { sourceCode.setPinned(false); } stateMachine.internalRemoveRelationships(Primitive.SOURCECODE); return true; } /** * Check if the response can be defined as a template based on the question, context. */ public Relationship checkTemplate(Vertex input, Network network) { if (!isEnabled()) { return null; } Language language = getBot().mind().getThought(Language.class); Vertex speaker = input.getRelationship(Primitive.SPEAKER); boolean learn = language.shouldLearn(input, speaker); boolean correction = input.hasRelationship(Primitive.ASSOCIATED, Primitive.CORRECTION); if (correction) { if (!language.shouldCorrect(input, speaker)) { return null; } } else if (!learn) { return null; } Vertex sentence = input.mostConscious(Primitive.INPUT); if (sentence == null || (!sentence.instanceOf(Primitive.SENTENCE))) { return null; } log("Checking sentence", Bot.FINE, sentence); Vertex questionInput = input.mostConscious(Primitive.QUESTION); if (questionInput == null) { log("No question", Bot.FINE, sentence); return null; } Vertex mimic = questionInput.getRelationship(Primitive.MIMIC); // If it was a mimic the response must be changed to be in the context of self, // and the question must be the question that was mimicked. if (mimic != null) { questionInput = mimic; questionInput = questionInput.copy(); questionInput.internalRemoveRelationships(Primitive.SPEAKER); questionInput.internalRemoveRelationships(Primitive.TARGET); questionInput.addRelationship(Primitive.SPEAKER, Primitive.SELF); questionInput.addRelationship(Primitive.TARGET, speaker); } // If it was a correction the response must be changed to be in the context of self, // and the question must be the original question that was corrected. if (correction) { input = input.copy(); input.internalRemoveRelationships(Primitive.SPEAKER); input.internalRemoveRelationships(Primitive.TARGET); input.addRelationship(Primitive.SPEAKER, Primitive.SELF); input.addRelationship(Primitive.TARGET, speaker); } Vertex question = questionInput.mostConscious(Primitive.INPUT); log("Checking question response", Bot.FINE, question, sentence); List<Vertex> words = question.orderedRelations(Primitive.WORD); if (words == null) { log("Question missing words", Bot.FINE, question); return null; } Map<Vertex, Vertex> variables = new HashMap<Vertex, Vertex>(); Vertex inputVariable = network.createVertex(Primitive.INPUT_VARIABLE); Bootstrap.checkInputVariable(inputVariable, network); variables.put(questionInput, inputVariable); Vertex newQuotient = createTemplate(questionInput, input, variables, network); Relationship relationship = null; if (newQuotient != null) { // Check if any existing templates match the new one. relationship = question.getRelationship(Primitive.RESPONSE, newQuotient); if (relationship != null) { log("Existing response template", Level.FINER, question, relationship.getTarget()); // Increase correctness. question.addRelationship(Primitive.RESPONSE, relationship.getTarget()); } else { log("Adding response template", Bot.FINE, question, newQuotient); relationship = question.addRelationship(Primitive.RESPONSE, newQuotient); question.associateAll(Primitive.WORD, question, Primitive.QUESTION); } network.checkReduction(question); question.associateAll(Primitive.SYNONYM, newQuotient, Primitive.RESPONSE); relationship.getTarget().addRelationship(Primitive.QUESTION, question); } return relationship; } /** * Check if any of the current states match the next word, * if no match, or quotient does not match, add a new case. * Return if the quotient matched. */ public boolean processState( Vertex currentState, List<Vertex> words, int index, String statePath, PathType path, Vertex sentence, Vertex questionInput, Vertex responseInput, Map<Vertex, Vertex> variables, Network network) { if (isStopped()) { return false; } if (index == words.size()) { return checkQuotient(currentState, sentence, questionInput, responseInput, path, variables, network); } Vertex currentWord = words.get(index); List<Vertex> instructions = currentState.orderedRelations(Primitive.DO); boolean caseFound = false; boolean anyQuotientMatch = false; if (instructions != null) { // Process all states that match recursively. for (Vertex instruction : instructions) { if (isStopped()) { return false; } if (instruction.instanceOf(Primitive.CASE)) { Vertex variable = instruction.getRelationship(Primitive.CASE); if (variable == null) { continue; } // Check what type of variable it is. boolean isWord = !variable.instanceOf(Primitive.VARIABLE); Vertex meaning = variable.getRelationship(Primitive.MEANING); boolean isMeaning = (meaning != null) && (!meaning.instanceOf(Primitive.VARIABLE)); boolean isEmptyMeaning = isMeaning && (!meaning.hasRelationship(Primitive.INSTANTIATION)); boolean isClassification = (meaning != null) && (meaning.instanceOf(Primitive.VARIABLE)); boolean isVerb = variable.instanceOf(Primitive.VERB) || ((meaning != null) && (meaning.instanceOf(Primitive.ACTION))); // Check that the variable type matches the current path type. if ((path == PathType.Meaning && (isWord || isMeaning)) // Simple word or direct meaning. || (path == PathType.Verb && (isWord || isEmptyMeaning || (isClassification && !isVerb) || (isMeaning && isVerb))) // Simple word/meaning/verb, or non-verb classification. || (path == PathType.Classification && (isClassification || isWord || isEmptyMeaning))) { // Simple word, or unclassified meaning, or classification variable. if (currentWord.matches(variable, variables) == Boolean.TRUE) { StringWriter pathWriter = new StringWriter(); pathWriter.write(statePath); if (variable.getName() == null) { pathWriter.write(String.valueOf(currentWord.getData())); } else { pathWriter.write(variable.getName()); } pathWriter.write("_"); Vertex nextState = instruction.getRelationship(Primitive.GOTO); caseFound = true; // Case matched, process next states. boolean quotientMatch = processState( nextState, words, index + 1, pathWriter.toString(), path, sentence, questionInput, responseInput, new HashMap<Vertex, Vertex>(variables), network); if (quotientMatch) { // Increase correctness of case. currentState.addRelationship(Primitive.DO, instruction); anyQuotientMatch = true; } } } } } } if (isStopped()) { return false; } // Add meaning or classification case. if (!caseFound) { StringWriter pathWriter = new StringWriter(); pathWriter.write(statePath); Vertex meaning = currentWord.mostConscious(Primitive.MEANING); // TODO, which meaning? or add case for all? boolean done = false; if (meaning != null) { Collection<Relationship> classifications = meaning.getRelationships(Primitive.INSTANTIATION); boolean isVerb = meaning.instanceOf(Primitive.ACTION); // If a Classification path type add a state case for a variable that matches the classifications of the word's meaning. // Also add case if verb path type and not a verb. if ((classifications != null) && ((path == PathType.Classification) || (!isVerb && (path == PathType.Verb)))) { done = true; Vertex newCase = network.createInstance(Primitive.CASE); Vertex variable = network.createInstance(Primitive.VARIABLE); variables.put(currentWord, variable); Vertex meaningVariable = network.createInstance(Primitive.VARIABLE); variable.addRelationship(Primitive.MEANING, meaningVariable); StringWriter variableNameWriter = new StringWriter(); boolean first = true; // For each classification add it to the variable and variable name. for (Relationship classification : classifications) { meaningVariable.addRelationship(Primitive.INSTANTIATION, classification.getTarget()); // Append the classification to the variable name. if (!first) { variableNameWriter.write("-"); } else { first = false; } String classificationName = classification.getTarget().getName(); if (classification.getTarget().isPrimitive()) { classificationName = ((Primitive)classification.getTarget().getData()).getIdentity(); } if (classificationName == null) { classificationName = currentWord.getDataValue(); } variableNameWriter.write(classificationName); } String variableName = Utils.compress(variableNameWriter.toString(), MAX_IDENTIFIER); newCase.setName("c" + newCase.getId() + "_" + variableName); variable.setName("v" + variable.getId() + "_" + variableName); meaningVariable.setName("v" + meaningVariable.getId() + "_" + variableName); pathWriter.write(variableName); variables.put(meaning, meaningVariable); newCase.addRelationship(Primitive.CASE, variable); Vertex newState = network.createInstance(Primitive.STATE); newState.setName("s" + newState.getId() + "_" + Utils.compress(pathWriter.toString(), MAX_IDENTIFIER)); pathWriter.write("_"); newCase.addRelationship(Primitive.GOTO, newState); currentState.addRelationship(Primitive.DO, newCase, Integer.MAX_VALUE); log("Adding new case", Level.FINER, currentWord, newCase, newState); processState( newState, words, index + 1, pathWriter.toString(), path, sentence, questionInput, responseInput, new HashMap<Vertex, Vertex>(variables), network); } } if (isStopped()) { return false; } if (!done) { // Add a specific case for the word or meaning. pathWriter.write(currentWord.getDataValue()); pathWriter.write("_"); Vertex newCase = network.createInstance(Primitive.CASE); newCase.setName("c" + newCase.getId() + "_" + Utils.compress(currentWord.getDataValue(), MAX_IDENTIFIER)); // The index decides if new case should be added to the beginning, or end. // TODO: variables should be ordered by number of relationships if (meaning != null) { Vertex variable = network.createInstance(Primitive.VARIABLE); variable.setName("v" + variable.getId() + "_" + Utils.compress(currentWord.getDataValue(), MAX_IDENTIFIER)); variables.put(currentWord, variable); variable.addRelationship(Primitive.MEANING, meaning); newCase.addRelationship(Primitive.CASE, variable); } else { newCase.addRelationship(Primitive.CASE, currentWord); } Vertex newState = network.createInstance(Primitive.STATE); newState.setName("s" + newState.getId() + "_" + Utils.compress(pathWriter.toString(), MAX_IDENTIFIER)); newCase.addRelationship(Primitive.GOTO, newState); currentState.addRelationship(Primitive.DO, newCase, 0); log("Adding new case", Level.FINER, currentWord, newCase, newState); processState( newState, words, index + 1, pathWriter.toString(), path, sentence, questionInput, responseInput, new HashMap<Vertex, Vertex>(variables), network); } } return anyQuotientMatch; } /** * Add the sentence from the previous input to the relationships response meta info. */ public void addSentencePreviousMeta(Relationship relationship, Vertex questionInput, Vertex previousQuestionInput, Map<Vertex, Vertex> variables, Network network) { // Associate previous question as meta info. if (previousQuestionInput != null) { Vertex previousQuestion = previousQuestionInput.getRelationship(Primitive.INPUT); if (previousQuestion != null) { Vertex meta = network.createMeta(relationship); Vertex newQuotient = createTemplate(questionInput, previousQuestionInput, variables, network); if (newQuotient == null) { newQuotient = previousQuestion; } meta.addRelationship(Primitive.PREVIOUS, newQuotient); } } } /** * Check if any of the existing quotients match, if none do, then add a new quotient. */ public boolean checkQuotient( Vertex currentState, Vertex sentence, Vertex questionInput, Vertex responseInput, PathType path, Map<Vertex, Vertex> variables, Network network) { // Check if any existing quotients match. Collection<Relationship> quotients = currentState.getRelationships(Primitive.QUOTIENT); Vertex previousQuestionInput = questionInput.getRelationship(Primitive.QUESTION); if (quotients != null) { for (Relationship quotient : quotients) { if (isStopped()) { return false; } Vertex quotientSentence = quotient.getTarget().getRelationship(Primitive.SENTENCE); if ((quotientSentence != null) && (quotientSentence == sentence)) { log("Existing quotient", Level.FINER, currentState, quotientSentence); // Increase correctness. Relationship relationship = currentState.addRelationship(Primitive.QUOTIENT, quotient.getTarget()); // Associate previous question as meta info. addSentencePreviousMeta(relationship, questionInput, previousQuestionInput, variables, network); return true; } } } if (isStopped()) { return false; } Vertex newQuotient = createTemplate(questionInput, responseInput, variables, network); if (newQuotient == null) { newQuotient = sentence; } else { // Check if any existing templates match the new one (by name). Relationship quotient = currentState.getRelationship(Primitive.QUOTIENT, newQuotient); if (quotient != null) { log("Existing quotient template", Level.FINER, currentState, quotient.getTarget()); // Increase correctness. Relationship relationship = currentState.addRelationship(Primitive.QUOTIENT, quotient.getTarget()); addSentencePreviousMeta(relationship, questionInput, previousQuestionInput, variables, network); return true; } } if (isStopped()) { return false; } // Only add variable quotients as possible until they are confirmed without multiple distinct matches. if ((path == PathType.Verb) || (path == PathType.Classification)) { Vertex question = questionInput.getRelationship(Primitive.INPUT); Relationship quotient = currentState.getRelationship(Primitive.POSSIBLE_QUOTIENT, newQuotient); if (quotient != null) { if (!quotient.getMeta().hasRelationship(Primitive.SENTENCE, question)) { // Must be a new sentence with same quotient, generalize it. currentState.addRelationship(Primitive.QUOTIENT, newQuotient); currentState.internalRemoveRelationship(quotient); log("Adding new generic quotient", Bot.FINE, currentState, newQuotient, question); return false; } else { log("Generic quotient already defined for sentence", Level.FINER, currentState, newQuotient, question); } } else { Relationship relationship = currentState.addRelationship(Primitive.POSSIBLE_QUOTIENT, newQuotient); Vertex meta = network.createMeta(relationship); meta.addRelationship(Primitive.SENTENCE, question); log("Adding new possible quotient", Level.FINE, currentState, newQuotient); } return false; } Relationship relationship = currentState.addRelationship(Primitive.QUOTIENT, newQuotient); addSentencePreviousMeta(relationship, questionInput, previousQuestionInput, variables, network); log("Adding new quotient", Level.FINE, currentState, newQuotient); return false; } /** * Attempt to create a template response from the question and response. */ public Vertex createTemplate(Vertex questionInput, Vertex responseInput, Map<Vertex, Vertex> variables, Network network) { Vertex response = responseInput.getRelationship(Primitive.INPUT); if (response == null || (!response.instanceOf(Primitive.SENTENCE)) || !response.hasRelationship(Primitive.WORD)) { return null; } log("Checking template", Level.FINER, questionInput, responseInput); boolean isTemplate = false; List<Vertex> templateWords = new ArrayList<Vertex>(); Vertex inputVariable = variables.get(questionInput); Vertex speaker = questionInput.getRelationship(Primitive.SPEAKER); if (speaker != null) { Vertex speakerVariable = inputVariable.getRelationship(Primitive.SPEAKER); Collection<Relationship> words = speaker.getRelationships(Primitive.WORD); if ((words != null) && (speakerVariable != null)) { for (Relationship word : words) { variables.put(word.getTarget(), speakerVariable); } } } Vertex target = questionInput.getRelationship(Primitive.TARGET); if (target != null) { Vertex targetVariable = inputVariable.getRelationship(Primitive.TARGET); Collection<Relationship> words = target.getRelationships(Primitive.WORD); if ((words != null) && (targetVariable != null)) { for (Relationship word : words) { variables.put(word.getTarget(), targetVariable); } } } // Index any word variables meanings so they are processed. for (Map.Entry<Vertex, Vertex> entry : new HashMap<Vertex, Vertex>(variables).entrySet()) { if (!(entry.getKey() instanceof Vertex)) { continue; } Vertex word = entry.getKey(); Vertex variable = entry.getValue(); if (word.instanceOf(Primitive.WORD) && variable.instanceOf(Primitive.VARIABLE)) { Vertex meaning = variable.mostConscious(Primitive.MEANING); if ((meaning != null) && !meaning.instanceOf(Primitive.VARIABLE)) { variables.put(meaning, meaning); } } } // Check each word in the sentence. for (Vertex word : response.orderedRelations(Primitive.WORD)) { // Check if any variables match the word. Vertex variable = variables.get(word); if (variable != null) { log("Template defined", Level.FINER, questionInput, word, variable); isTemplate = true; templateWords.add(variable); } else { // Next check if any variable value has a relationship based on any other variable value. // TODO variables should be ordered, and only use each once. boolean match = false; for (Map.Entry<Vertex, Vertex> entry : variables.entrySet()) { if (!(entry.getKey() instanceof Vertex)) { continue; } Vertex value = entry.getKey(); variable = entry.getValue(); if (!value.instanceOf(Primitive.VARIABLE) && !value.instanceOf(Primitive.WORD) && !value.instanceOf(Primitive.SENTENCE)) { // Check any of the variable's value's words match the word. Collection<Relationship> words = value.getRelationships(Primitive.WORD); if (words != null) { for (Relationship valueWord : words) { if (word == valueWord) { log("Template defined", Level.FINER, questionInput, word, variable); match = true; isTemplate = true; templateWords.add(variable); break; } } } for (Map.Entry<Vertex, Vertex> typeEntry : variables.entrySet()) { if (!(typeEntry.getKey() instanceof Vertex)) { continue; } Vertex associateType = typeEntry.getKey(); if (associateType != value && !associateType.instanceOf(Primitive.WORD) && !associateType.instanceOf(Primitive.VARIABLE) && !associateType.instanceOf(Primitive.SENTENCE)) { Collection<Relationship> associations = value.getRelationships(associateType); if (associations != null) { for (Relationship association : associations) { if (!association.getTarget().instanceOf(Primitive.SENTENCE)) { words = association.getTarget().getRelationships(Primitive.WORD); if (words != null) { for (Relationship valueWord : words) { if (word == valueWord) { Vertex expression = network.createInstance(Primitive.EXPRESSION); expression.addRelationship(Primitive.OPERATOR, Primitive.GET); expression.addRelationship(Primitive.ARGUMENT, variable); expression.addRelationship(Primitive.ARGUMENT, typeEntry.getValue()); match = true; log("Template defined", Level.FINER, questionInput, word, expression); isTemplate = true; templateWords.add(expression); break; } } } } if (match) { break; } } } } if (match) { break; } } } } if (!match) { // Next check if any variables has any relationships to the word. // TODO variables should be ordered, and only use each once. for (Map.Entry<Vertex, Vertex> entry : variables.entrySet()) { if (!(entry.getKey() instanceof Vertex)) { continue; } Vertex value = entry.getKey(); variable = entry.getValue(); // Don't check words, only meanings. if (!value.instanceOf(Primitive.VARIABLE) && !value.instanceOf(Primitive.WORD) && !value.instanceOf(Primitive.SENTENCE)) { Iterator<Relationship> relationships = value.allRelationships(); while (relationships.hasNext()) { Relationship relation = relationships.next(); if (!relation.isInverse() && !relation.getTarget().instanceOf(Primitive.SENTENCE)) { Collection<Relationship> words = relation.getTarget().getRelationships(Primitive.WORD); if (words != null) { for (Relationship valueWord : words) { if (word == valueWord.getTarget()) { Vertex expression = network.createInstance(Primitive.EXPRESSION); expression.addRelationship(Primitive.OPERATOR, Primitive.GET); expression.addRelationship(Primitive.ARGUMENT, variable); expression.addRelationship(Primitive.ARGUMENT, relation.getType()); match = true; log("Template defined", Level.FINER, questionInput, word, expression); isTemplate = true; templateWords.add(expression); break; } } } } if (match) { break; } } } } } if (!match) { templateWords.add(word); } } } if (isTemplate) { Vertex template = network.createInstance(Primitive.FORMULA); template.addRelationship(Primitive.INSTANTIATION, Primitive.SENTENCE); template.addRelationship(Primitive.SENTENCE, response); int index = 0; for (Vertex word : templateWords) { template.addRelationship(Primitive.WORD, word, index); index++; } template = SelfDecompiler.getDecompiler().createUniqueTemplate(template, network); return template; } log("Not a template", Level.FINER, questionInput, responseInput); return null; } }