/******************************************************************************* * CogTool Copyright Notice and Distribution Terms * CogTool 1.3, Copyright (c) 2005-2013 Carnegie Mellon University * This software is distributed under the terms of the FSF Lesser * Gnu Public License (see LGPL.txt). * * CogTool is free software; you can redistribute it and/or modify * it under the terms of the GNU Lesser General Public License as published by * the Free Software Foundation; either version 2.1 of the License, or * (at your option) any later version. * * CogTool is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU Lesser General Public License for more details. * * You should have received a copy of the GNU Lesser General Public License * along with CogTool; if not, write to the Free Software * Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA * * CogTool makes use of several third-party components, with the * following notices: * * Eclipse SWT version 3.448 * Eclipse GEF Draw2D version 3.2.1 * * Unless otherwise indicated, all Content made available by the Eclipse * Foundation is provided to you under the terms and conditions of the Eclipse * Public License Version 1.0 ("EPL"). A copy of the EPL is provided with this * Content and is also available at http://www.eclipse.org/legal/epl-v10.html. * * CLISP version 2.38 * * Copyright (c) Sam Steingold, Bruno Haible 2001-2006 * This software is distributed under the terms of the FSF Gnu Public License. * See COPYRIGHT file in clisp installation folder for more information. * * ACT-R 6.0 * * Copyright (c) 1998-2007 Dan Bothell, Mike Byrne, Christian Lebiere & * John R Anderson. * This software is distributed under the terms of the FSF Lesser * Gnu Public License (see LGPL.txt). * * Apache Jakarta Commons-Lang 2.1 * * This product contains software developed by the Apache Software Foundation * (http://www.apache.org/) * * jopt-simple version 1.0 * * Copyright (c) 2004-2013 Paul R. Holser, Jr. * * Permission is hereby granted, free of charge, to any person obtaining * a copy of this software and associated documentation files (the * "Software"), to deal in the Software without restriction, including * without limitation the rights to use, copy, modify, merge, publish, * distribute, sublicense, and/or sell copies of the Software, and to * permit persons to whom the Software is furnished to do so, subject to * the following conditions: * * The above copyright notice and this permission notice shall be * included in all copies or substantial portions of the Software. * * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, * EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF * MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND * NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE * LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION * OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION * WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. * * Mozilla XULRunner 1.9.0.5 * * The contents of this file are subject to the Mozilla Public License * Version 1.1 (the "License"); you may not use this file except in * compliance with the License. You may obtain a copy of the License at * http://www.mozilla.org/MPL/. * Software distributed under the License is distributed on an "AS IS" * basis, WITHOUT WARRANTY OF ANY KIND, either express or implied. See the * License for the specific language governing rights and limitations * under the License. * * The J2SE(TM) Java Runtime Environment version 5.0 * * Copyright 2009 Sun Microsystems, Inc., 4150 * Network Circle, Santa Clara, California 95054, U.S.A. All * rights reserved. U.S. * See the LICENSE file in the jre folder for more information. ******************************************************************************/ package edu.cmu.cs.hcii.cogtool.model; import java.io.BufferedWriter; import java.io.File; import java.io.FileOutputStream; import java.io.IOException; import java.io.OutputStreamWriter; import java.util.ArrayList; import java.util.Collection; import java.util.Iterator; import java.util.List; import java.util.regex.Matcher; import java.util.regex.Pattern; import edu.cmu.cs.hcii.cogtool.model.ISimilarityDictionary.DictEntry; import edu.cmu.cs.hcii.cogtool.model.ISimilarityDictionary.DictValue; import edu.cmu.cs.hcii.cogtool.util.Cancelable; import edu.cmu.cs.hcii.cogtool.util.IAttributed; import edu.cmu.cs.hcii.cogtool.util.KeyDisplayUtil; import edu.cmu.cs.hcii.cogtool.util.NamedObject; import edu.cmu.cs.hcii.cogtool.util.ObjectLoader; import edu.cmu.cs.hcii.cogtool.util.ObjectSaver; import edu.cmu.cs.hcii.cogtool.util.ProcessTraceCallback; import edu.cmu.cs.hcii.cogtool.util.Subprocess; public class SNIFACTPredictionAlgo extends APredictionAlgo { public static SNIFACTPredictionAlgo ONLY = new SNIFACTPredictionAlgo(); protected static StepExtractor[] STEP_EXTRACTORS = { new ClickStepExtractor(), new TypeStepExtractor(), new LookStepExtractor(), new TapStepExtractor() }; protected SNIFACTParameters parameters; protected static final String RUN_MARKER = "<<< CT-Explorer: between runs marker >>>"; public static int edu_cmu_cs_hcii_cogtool_model_SNIFACTPredictionAlgo_version = 0; private static ObjectSaver.IDataSaver<SNIFACTPredictionAlgo> SAVER = new ObjectSaver.ADataSaver<SNIFACTPredictionAlgo>() { @Override public int getVersion() { return edu_cmu_cs_hcii_cogtool_model_SNIFACTPredictionAlgo_version; } @Override public void saveData(SNIFACTPredictionAlgo v, ObjectSaver saver) { // Nothing to save; it's an ONLY! } }; public static void registerSaver() { ObjectSaver.registerSaver(SNIFACTPredictionAlgo.class.getName(), SAVER); } private static ObjectLoader.IObjectLoader<SNIFACTPredictionAlgo> LOADER = new ObjectLoader.AObjectLoader<SNIFACTPredictionAlgo>() { @Override public SNIFACTPredictionAlgo createObject() { return ONLY; } }; public static void registerLoader() { ObjectLoader.registerLoader(SNIFACTPredictionAlgo.class.getName(), edu_cmu_cs_hcii_cogtool_model_SNIFACTPredictionAlgo_version, LOADER); } public static final int NO_BACK = 0; public static final int EXPLICT_BACK = 1; public static final int IMPLICIT_BACK = 2; public static File exportCTEModelFile = null; /** * The five parameters that the SNIF-ACT algorithm needs for execution. * @author rmyers */ public static class SNIFACTParameters { public static final int edu_cmu_cs_hcii_cogtool_model_SNIFACTParameters_version = 0; protected static final String taskNameVAR = "taskName"; protected static final String numRunsVAR = "numRuns"; protected static final String kValueVAR = "kValue"; protected static final String startFrameVAR = "startFrame"; protected static final String targetFramesVAR = "targetFrames"; protected static final String algorithmVAR = "algorithm"; private static ObjectSaver.IDataSaver<SNIFACTParameters> SAVER = new ObjectSaver.ADataSaver<SNIFACTParameters>() { @Override public int getVersion() { return edu_cmu_cs_hcii_cogtool_model_SNIFACTParameters_version; } @Override public void saveData(SNIFACTParameters v, ObjectSaver saver) throws java.io.IOException { saver.saveObject(v.taskName, taskNameVAR); saver.saveInt(v.numRuns, numRunsVAR); saver.saveInt(v.kValue, kValueVAR); saver.saveObject(v.startFrame, startFrameVAR); saver.saveObject(v.targetFrames, targetFramesVAR); saver.saveObject(v.algorithm, algorithmVAR); } }; public static void registerSaver() { ObjectSaver.registerSaver(SNIFACTParameters.class.getName(), SAVER); } private static ObjectLoader.IObjectLoader<SNIFACTParameters> LOADER = new ObjectLoader.AObjectLoader<SNIFACTParameters>() { @Override public SNIFACTParameters createObject() { return new SNIFACTParameters(); } @Override public void set(SNIFACTParameters target, String variable, Object value) { if (variable != null) { if (variable.equals(taskNameVAR)) { target.taskName = (String) value; } else if (variable.equals(startFrameVAR)) { target.startFrame = (String) value; } else if (variable.equals(algorithmVAR)) { target.algorithm = (ITermSimilarity) value; } } } @Override public void set(SNIFACTParameters target, String variable, int value) { if (variable != null) { if (variable.equals(numRunsVAR)) { target.numRuns = value; } else if (variable.equals(kValueVAR)) { target.kValue = value; } } } @Override public Collection<?> createCollection(SNIFACTParameters target, String variable, int size) { if (variable != null) { if (variable.equals(targetFramesVAR)) { target.targetFrames = new ArrayList<String>(); return target.targetFrames; } } return null; } }; public static void registerLoader() { ObjectLoader.registerLoader(SNIFACTParameters.class.getName(), edu_cmu_cs_hcii_cogtool_model_SNIFACTParameters_version, LOADER); } public String taskName; public int numRuns; public int kValue; public String startFrame; public List<String> targetFrames; public ITermSimilarity algorithm; private SNIFACTParameters() { // for loading } public SNIFACTParameters(String task, int runs, int value, String frameName, List<String> targets, ITermSimilarity dictAlg) { taskName = task; numRuns = runs; kValue = value; startFrame = frameName; targetFrames = targets; algorithm = dictAlg; } } /** * The extra parameter does not need to be passed to SNIF-ACT, but the * interaction needs to know about it. It specifies whether the new tasks * will be added to the same task group or a new one. * @author rmyers */ public static class SNIFACTGroupParameters extends SNIFACTParameters { public static final int edu_cmu_cs_hcii_cogtool_model_SNIFACTGroupParameters_version = 0; protected static final String addToGroupVAR = "addToGroup"; private static ObjectSaver.IDataSaver<SNIFACTGroupParameters> SAVER = new ObjectSaver.ADataSaver<SNIFACTGroupParameters>() { @Override public int getVersion() { return edu_cmu_cs_hcii_cogtool_model_SNIFACTGroupParameters_version; } @Override public void saveData(SNIFACTGroupParameters v, ObjectSaver saver) throws java.io.IOException { saver.saveBoolean(v.addToGroup, addToGroupVAR); } }; public static void registerSaver() { ObjectSaver.registerSaver(SNIFACTGroupParameters.class.getName(), SAVER); } private static ObjectLoader.IObjectLoader<SNIFACTGroupParameters> LOADER = new ObjectLoader.AObjectLoader<SNIFACTGroupParameters>() { @Override public SNIFACTGroupParameters createObject() { return new SNIFACTGroupParameters(); } @Override public void set(SNIFACTGroupParameters target, String variable, boolean value) { if (variable != null) { if (variable.equals(addToGroupVAR)) { target.addToGroup = value; } } } }; public static void registerLoader() { ObjectLoader.registerLoader(SNIFACTGroupParameters.class.getName(), edu_cmu_cs_hcii_cogtool_model_SNIFACTGroupParameters_version, LOADER); } public boolean addToGroup; private SNIFACTGroupParameters() { // for loading } public SNIFACTGroupParameters(String task, int runs, int value, String frameName, List<String> targets, ITermSimilarity dictAlg, boolean add) { super(task, runs, value, frameName, targets, dictAlg); addToGroup = add; } } protected class SNIFACTAnalysisOutput implements IAnalysisOutput { protected Design design; protected List<List<AScriptStep>> scriptStepLists; protected List<List<ResultStep>> resultStepList; protected List<List<String>> traces; protected List<String> errorLines; public SNIFACTAnalysisOutput(Design d, List<List<AScriptStep>> stepLists, List<List<ResultStep>> results, List<List<String>> traceList, List<String> errors) { design = d; scriptStepLists = stepLists; resultStepList = results; traces = traceList; errorLines = errors; } /** * For each list of scriptSteps parsed by the SNIFACTAnalysisInput, * create a task and task application, and fill out the demonstration * with the list of script steps. The work thread's doneCallback * method will situate the tasks in the task group and the task * applications in the project. The results returned in this aggregate * result object save all the relevant data. */ public APredictionResult completeWork() { List<APredictionResult> results = new ArrayList<APredictionResult>(); for (int i = 0; i < scriptStepLists.size(); i++) { List<AScriptStep> steps = scriptStepLists.get(i); List<String> traceLines = traces.get(i); List<ResultStep> resultSteps = resultStepList.get(i); int lineCount = traceLines.size(); String lastLine = traceLines.get(lineCount - 1); Task t = new Task(); TaskApplication ta = new TaskApplication(t, design); Demonstration d = ta.getDemonstration(); d.appendSteps(steps); d.setStartFrameChosen(true); d.setStartFrame(design.getFrame(parameters.startFrame)); double taskTime = -1.0; TimePredictionResult result = null; try { taskTime = Double.parseDouble(lastLine); } catch (NumberFormatException e) { // -1.0 is a flag indicating failure, so don't need to do // anything here (but don't crash!) } result = new SNIFACTPredictionResult(parameters.taskName, SNIFACTPredictionAlgo.ONLY, traceLines, errorLines, resultSteps, taskTime, ta); if (taskTime == -1.0) { result.setResultState(APredictionResult.COMPUTE_FAILED); } else { result.setResultState(APredictionResult.IS_COMPUTED); } results.add(result); } List<String> traceLines = new ArrayList<String>(); for (List<String> traceList : traces) { traceLines.addAll(traceList); } return new TimeDistributionPredictionResult(parameters.taskName, null, SNIFACTPredictionAlgo.ONLY, traceLines, errorLines, results); } } /** * Do not call this function! For demo purposes only! */ // protected static IWidget getWidgetFromLabel(Frame f, String displayLabel) // { // Iterator<IWidget> widgets = f.getWidgets().iterator(); // // while (widgets.hasNext()) { // IWidget w = widgets.next(); // // if (w.getTitle().equals(displayLabel)) { // return w; // } // } // // return null; // } // Warning: non-reentrancy, here. This is a rather dirty bit of global state // allowing backchannel communication between the Look-At step extractor, // and the Click and Tap step extractors. private static String lastLookedAtTarget = null; /** * Support for extracting script steps from ResultSteps in a parsed ACT-R trace * @author rmyers */ private interface StepExtractor { public static final ResultStep FLUSH = null; /** * Returns true if the given resource string and operation apply to * the script step this object knows how to create */ public boolean matchesResultStep(ResultStep step); /** * If step is not FLUSH, parses and saves data from it (this may result * in a script step being generated). Otherwise, the extractor should * accumulate all of its stored data, generate a script step, and reset * the state. */ public AScriptStep generateStep(ResultStep step, Frame currentFrame); } /** * This extractor recognizes mouse clicks (and the moves to the widgets to * be clicked on). It guesses as best it can as to the nature of multiple * consecutive clicks on the same widget. * @author rmyers */ private static class ClickStepExtractor implements StepExtractor { private static final Pattern MOVE_PAT = Pattern.compile( "Move Cursor to (.+?) in (.+)"); private static final String CLICK_OP = "Click Mouse"; private String targetName; private String frameName; private int numClicks = 0; private boolean needMove = false; public boolean matchesResultStep(ResultStep step) { if (ResultStep.MOTOR_LEFT_EXEC_RESOURCE.equals(step.resource) || ResultStep.MOTOR_RIGHT_EXEC_RESOURCE.equals(step.resource)) { // If it is a move, record the target widget of the move. // Otherwise, check if it is a click. Matcher m = MOVE_PAT.matcher(step.operation); if (m.matches()) { targetName = m.group(1); frameName = m.group(2); needMove = true; return true; } return step.operation.equals(CLICK_OP); } return false; } /** * If it has no target name stored, there's nothing it can do. If * the target exists and the step desired is a move step, create that * step. If it is not a FLUSH request, increment the number of * consecutive clicks seen. If we've seen three, generate a triple * click step and reset the state; otherwise, there's no reason to * return anything. If it is a FLUSH request, create a step with the * appropriate number of clicks and reset the state. */ public AScriptStep generateStep(ResultStep step, Frame currentFrame) { if (targetName == null) { if (lastLookedAtTarget != null) { targetName = lastLookedAtTarget; } else { System.err.println("confusion parsing result steps, no move before click"); return null; } } Design d = currentFrame.getDesign(); Frame f = null; if (d != null) { f = d.getFrame(frameName); } IWidget target = null; if (f != null) { target = f.getWidget(targetName); } if (target == null) { System.err.println(String.format( "confusion parsing result steps, can't find widget (%s, %s, %s, %s)", d, frameName, f, targetName)); return null; } if (needMove) { needMove = false; MoveMouseAction action = new MoveMouseAction(); return new ActionScriptStep(action, target); } if (step != FLUSH) { numClicks++; if (numClicks == 3) { // If we see three clicks in a row on the same widget // before a move, generate a triple-click script step. numClicks = 0; AAction action = new ButtonAction(MouseButtonState.Left, MousePressType.Triple, AAction.NONE); return new ActionScriptStep(action, target); } return null; } AAction action; if (numClicks == 1) { action = new ButtonAction(MouseButtonState.Left, MousePressType.Click, AAction.NONE); } else if (numClicks == 2) { action = new ButtonAction(MouseButtonState.Left, MousePressType.Double, AAction.NONE); } else { // should not be 0 or 3 or higher return null; } numClicks = 0; return new ActionScriptStep(action, target); } } /** * This extractor recognizes mouse clicks (and the moves to the widgets to * be clicked on). It guesses as best it can as to the nature of multiple * consecutive clicks on the same widget. * @author rmyers */ private static class TapStepExtractor implements StepExtractor { private static final Pattern MOVE_PAT = Pattern.compile( "Move Finger to (.+?) in (.+)"); private static final String TAP_OP = "Tap"; private String targetName; private String frameName; private int numClicks = 0; public boolean matchesResultStep(ResultStep step) { if (ResultStep.MOTOR_LEFT_EXEC_RESOURCE.equals(step.resource) || ResultStep.MOTOR_RIGHT_EXEC_RESOURCE.equals(step.resource)) { // For taps, there is just a move operation, because that // includes the action of the tap. Subsequent taps work the // same way as subsequent "Click Mouse" steps. Matcher m = MOVE_PAT.matcher(step.operation); if (m.matches()) { targetName = m.group(1); frameName = m.group(2); return true; } return step.operation.equals(TAP_OP); } return false; } /** * If it has no target name stored, there's nothing it can do. If * the target exists and the step desired is a move step, create that * step. If it is not a FLUSH request, increment the number of * consecutive clicks seen. If we've seen three, generate a triple * click step and reset the state; otherwise, there's no reason to * return anything. If it is a FLUSH request, create a step with the * appropriate number of clicks and reset the state. */ public AScriptStep generateStep(ResultStep step, Frame currentFrame) { if (targetName == null) { if (lastLookedAtTarget != null) { targetName = lastLookedAtTarget; } else { return null; } } IWidget target = currentFrame.getDesign().getFrame(frameName).getWidget(targetName); if (target == null) { return null; } if (step != FLUSH) { numClicks++; if (numClicks == 3) { // If we see three clicks in a row on the same widget // before a move, generate a triple-click script step. numClicks = 0; AAction action = new TapAction(TapPressType.TripleTap); return new ActionScriptStep(action, target); } return null; } AAction action; if (numClicks == 1) { action = new TapAction(TapPressType.Tap); } else if (numClicks == 2) { action = new TapAction(TapPressType.DoubleTap); } else { // should not be 0 or 3 or higher return null; } numClicks = 0; return new ActionScriptStep(action, target); } } /** * This extractor recognizes key presses. Since the trace parser stores * individual key presses in result steps, the extractor has to save * characters as they come in and accumulate them until it is told to flush * (i.e. when a result step in the trace is matched by a different * extractor). * (note: currently no way of differentiating multiple keyboard transitions * in a row from one long one with the same key sequence) * @author rmyers */ private static class TypeStepExtractor implements StepExtractor { private static final String PRESS_PREFIX = "Press Key "; private StringBuilder actionString = new StringBuilder(); public boolean matchesResultStep(ResultStep step) { if (ResultStep.MOTOR_LEFT_EXEC_RESOURCE.equals(step.resource) || ResultStep.MOTOR_RIGHT_EXEC_RESOURCE.equals(step.resource)) { return step.operation.startsWith(PRESS_PREFIX); } return false; } public AScriptStep generateStep(ResultStep step, Frame currentFrame) { if (step != FLUSH) { // step.operation is "Press Key "<key>"", so // convertMenuTextToAction trims quotation marks int prefixLength = PRESS_PREFIX.length(); if (step.operation.length() > prefixLength) { String substring = step.operation.substring(prefixLength); substring = KeyDisplayUtil.convertMenuTextToAction(substring); actionString.append(substring); } return null; } int length = actionString.length(); if (length == 0) { return null; } KeyAction action = new KeyAction(actionString.toString(), false, AAction.NONE); actionString.delete(0, length); return new ActionScriptStep(action, currentFrame.getInputDevice(DeviceType.Keyboard)); } } private static final Pattern IMPLICIT_GROUP_PAT = Pattern.compile("Group \\[i(\\d+)\\]", Pattern.CASE_INSENSITIVE); /** * This extractor uses the visual encoding result step to detect that a * widget is being looked at, and generates the corresponding script step. * @author rmyers */ private static class LookStepExtractor implements StepExtractor { private String targetName = null; private String frameName = null; public boolean matchesResultStep(ResultStep step) { return ResultStep.VISION_ENC_RESOURCE.equals(step.resource); } public AScriptStep generateStep(ResultStep step, Frame currentFrame) { if (step == FLUSH) { return null; } // step.operation is "<Widget name> in <frame name>" String[] names = step.operation.split(" in "); if (names.length == 2) { targetName = names[0]; lastLookedAtTarget = targetName; frameName = names[1]; } if (targetName == null) { return null; } Frame f = currentFrame.getDesign().getFrame(frameName); IWidget target = f.getWidget(targetName); // IWidget target = getWidgetFromLabel(currentFrame, this.targetName); if (target != null) { targetName = null; return new LookAtScriptStep(target); } // CT-E will look at a group to do hierarchical visual search. // But within CogTool groups aren't widgets, and can't be looked // at. So, we fool the script editor into displaying an impossible // look-at operation by creating a disembodied, non-interactive // widget. Note that this widget is *not* made a part of the // design. It's just an isolated little wart hanging off to the side, // that the design doesn't know anything about, but that contains // the appropriate name string and a pointer to the appropriate // frame in the design. IAttributed grp = f.getEltGroup(targetName); if (grp == null) { Matcher m = IMPLICIT_GROUP_PAT.matcher(targetName); if (m.matches()) { NamedObject obj = ACTRPredictionAlgo.getImplicitGroup(Integer.parseInt(m.group(1)), f); if (obj instanceof IAttributed) { grp = (IAttributed)obj; } } } if (grp != null) { Widget w = new Widget(new DoubleRectangle(0.0, 0.0, 1.0, 1.0), WidgetType.Noninteractive); IWidget remoteLabel = (IWidget)grp.getAttribute(WidgetAttributes.REMOTE_LABEL_ATTR); String title = null; if (remoteLabel != null) { title = remoteLabel.getTitle(); } if (title != null) { w.setName(String.format("%s (%s)", title, targetName)); } else { w.setName(targetName); } targetName = null; w.setFrame(f); return new LookAtScriptStep(w); } return null; } } protected class SNIFACTAnalysisInput extends AAnalysisInput { private Design design; private String lispMem; private File file; private String cmd; public SNIFACTAnalysisInput(Design d, String imageFile, String inputFile, String cmdStr) { design = d; lispMem = imageFile; file = new File(inputFile); cmd = cmdStr; } public IAnalysisOutput compute(ProcessTraceCallback progressCallback, Cancelable cancelable) { // A list of trace lines generated by SNIF-ACT; will be accumulated // in one large list and parsed later into a list of lists. List<String> traces = new ArrayList<String>(); // A list of lists of script steps, to be parsed from the // multiple ACT-R traces List<List<AScriptStep>> stepLists = new ArrayList<List<AScriptStep>>(); // Saving the results of all of the trace parsings for the // prediction result object List<List<ResultStep>> resultStepList = new ArrayList<List<ResultStep>>(); List<String> errorLines = new ArrayList<String>(); List<File> files = new ArrayList<File>(); files.add(file); try { Subprocess.execLisp(lispMem, files, cmd, traces, errorLines, progressCallback, cancelable); // ignore return value } catch (Subprocess.ExecuteException ex) { throw new ComputationException("Executing LISP failed", ex); } List<List<String>> traceList = parseTracelineLists(traces); // iterate over the runs, setting traceLines to each // run's trace for (List<String> traceLines : traceList) { // two kinds of steps here: stepList collections the ScriptSteps // that we'll populate the script editor with, while resultSteps // is the visualization result steps, a different animal List<AScriptStep> stepList = new ArrayList<AScriptStep>(); List<ResultStep> resultSteps = (new ACTRTraceParser()).parseTrace(traceLines); resultStepList.add(resultSteps); StepExtractor currentExtractor = null; Frame currentFrame = design.getFrame(parameters.startFrame); Frame newFrame = null; for (ResultStep step : resultSteps) { StepExtractor nextExtractor = null; for (StepExtractor element : STEP_EXTRACTORS) { if (element.matchesResultStep(step)) { // no step should match two different extractors nextExtractor = element; break; } } if (nextExtractor == null) { continue; } // First compare nextExtractor to currentExtractor; if they // are different, flush currentExtractor before updating it. if (currentExtractor != nextExtractor) { if (currentExtractor != null) { AScriptStep scriptStep = currentExtractor.generateStep(StepExtractor.FLUSH, currentFrame); if (scriptStep != null) { stepList.add(scriptStep); newFrame = scriptStep.getDestinationFrame(); } } currentExtractor = nextExtractor; } // Update the internal state of the current extractor based on // the current step. This may cause a script step to be // generated, so add it to the list if so. if (currentExtractor != null) { AScriptStep scriptStep = currentExtractor.generateStep(step, currentFrame); if (scriptStep != null) { stepList.add(scriptStep); newFrame = scriptStep.getDestinationFrame(); } } } // If necessary, flush the extractor for the last step in the trace. if (currentExtractor != null) { AScriptStep scriptStep = currentExtractor.generateStep(StepExtractor.FLUSH, currentFrame); if (scriptStep != null) { stepList.add(scriptStep); } } stepLists.add(stepList); if (newFrame != null) { currentFrame = newFrame; } } return new SNIFACTAnalysisOutput(design, stepLists, resultStepList, traceList, errorLines); } } protected String createSimilarityScoresFile(ISimilarityDictionary dict) { // if (dict.size() == 0) { // return null; // TODO ask about // } OutputStreamWriter fw = null; BufferedWriter buffer = null; String scoresPath = null; try { File dest = File.createTempFile("scores", ".lisp"); dest.deleteOnExit(); fw = new OutputStreamWriter(new FileOutputStream(dest), "US-ASCII"); buffer = new BufferedWriter(fw); Iterator<DictEntry> entries = dict.getEntries().iterator(); while (entries.hasNext()) { DictEntry entry = entries.next(); if ((parameters.algorithm == ITermSimilarity.ALL) || entry.algorithm.getClass().isInstance(parameters.algorithm)) { DictValue value = dict.getValue(entry); double simil = (value.similarity == ITermSimilarity.UNKNOWN) ? 0.0 : value.similarity; // escape any \'s and "'s in the input strings String quotedGoal = entry.goalWord.replaceAll("\\\\", "\\\\\\\\").replaceAll("\"", "\\\\\""); String quotedSearch = entry.searchWord.replaceAll("\\\\", "\\\\\\\\").replaceAll("\"", "\\\\\""); buffer.write("(store-score \"" + quotedGoal + "\" \""); buffer.write(quotedSearch + "\" " + simil + ")\n"); } } scoresPath = dest.getAbsolutePath(); } catch (IOException e) { throw new ComputationException("Writing file failed", e); } finally { try { if (buffer != null) { buffer.close(); } if (fw != null) { fw.close(); } } catch (IOException e) { throw new ComputationException("Closing writer failed", e); } } return scoresPath; } /** * SNIF-ACT returns a single trace file for all of its runs, so return a * list of lists of trace lines. */ protected List<List<String>> parseTracelineLists(List<String> traceLines) { List<List<String>> traces = new ArrayList<List<String>>(); List<String> curList = new ArrayList<String>(); Iterator<String> lines = traceLines.iterator(); while (lines.hasNext()) { String line = lines.next(); if (RUN_MARKER.equals(line)) { traces.add(curList); curList = new ArrayList<String>(); } else { curList.add(line); } } traces.add(curList); return traces; } protected SNIFACTPredictionAlgo() { } public void setParameters(SNIFACTParameters parms) { parameters = parms; } @Override public IAnalysisInput prepareComputation(Design design) { ISimilarityDictionary dict = (ISimilarityDictionary) design.getAttribute(WidgetAttributes.DICTIONARY_ATTR); String path = createSimilarityScoresFile(dict); File actrFile = null; boolean oldEmitVirtualFrames = ACTRPredictionAlgo.emitVirtualFrames; try { if (exportCTEModelFile == null) { // Create a temp file to hold the ACT-R model actrFile = File.createTempFile("cogtool-actr-model-", ".lisp"); actrFile.deleteOnExit(); } else { actrFile = exportCTEModelFile; } ACTRPredictionAlgo.emitVirtualFrames = true; ACTR6PredictionAlgo.ONLY.outputModel(design, null, actrFile, path, parameters); } catch (IOException e) { throw new ComputationException("IOException creating ACT-R model", e); } finally { ACTRPredictionAlgo.emitVirtualFrames = oldEmitVirtualFrames; } return new SNIFACTAnalysisInput(design, "actr6.mem", actrFile.getAbsolutePath(), "*cogtool-result*"); } }