package org.geogebra.common.kernel.cas; import java.util.TreeSet; import org.geogebra.common.kernel.AlgoCasCellInterface; import org.geogebra.common.kernel.StringTemplate; import org.geogebra.common.kernel.algos.AlgoElement; import org.geogebra.common.kernel.algos.Algos; import org.geogebra.common.kernel.geos.GeoCasCell; import org.geogebra.common.kernel.geos.GeoElement; /** * Algorithm to take care of GeoCasCells and possibly other GeoElements in the * construction. This algorithm updates a given output GeoCasCell (e.g. m := c + * 3) and possibly a twin GeoElement object (e.g. GeoNumeric m = c + 3 when c is * defined). * * @author Markus Hohenwarter */ public class AlgoDependentCasCell extends AlgoElement implements AlgoCasCellInterface { // output CAS cell of this algorithm private GeoCasCell casCell; /** * Creates a new algorithm to handle updates of the given cell. * * @param casCell * the output cell that this algorithm should update. */ public AlgoDependentCasCell(GeoCasCell casCell) { super(casCell.getConstruction()); this.casCell = casCell; setProtectedInput(true); // make sure all input geos' values are present in the CAS initInput(); // We need to compute the output so that the // arbitrary constants are created before the // definition in the saved file if (casCell.isOutputEmpty()) { compute(); } else { casCell.updateTwinGeo(false); } // initialize algorithm dependencies setInputOutput(); // tell construction that order of CAS cells may have changed cons.updateCasCellRows(); // setLabel of twinGeo if we got one casCell.setLabelOfTwinGeo(); if (casCell.getInputVE() != null && casCell.getInputVE().isTopLevelCommand("SlopeField")) { cons.registerEuclidianViewCE(this); } } @Override public Algos getClassName() { return Algos.Expression; } private void initInput() { // input // m := c + 3 has input variable c TreeSet<GeoElement> geoVars = casCell.getGeoElementVariables(); if (geoVars == null) { input = new GeoElement[0]; } else { GeoElement[] geos = new GeoElement[geoVars.size()]; input = geoVars.toArray(geos); // If the cell contains circular definition, reload might cause // stack overflow for (int i = 0; i < input.length; i++) { if (input[i] == casCell) { input = new GeoElement[0]; return; } } } } /** * Initializes input and output dependencies of this algorithm. This method * can be called again after the GeoCasCell's input expression has changed. */ @Override protected void setInputOutput() { // init output // twin geo that may be created as a side effect // e.g. the CAS cell m := c + 3 will create a GeoNumeric m GeoElement twinGeo = casCell.getTwinGeo(); setOutputLength(twinGeo == null ? 1 : 2); setOutput(0, casCell); if (twinGeo != null) { setOutput(1, twinGeo); } // set input and output dependencies setDependencies(); } /** * @return resulting CAS cell */ @Override public GeoCasCell getCasCell() { return casCell; } @Override public void compute() { // check if all input variables are defined boolean inputDefined = true; for (GeoElement geo : input) { if (!geo.isDefined()) { inputDefined = false; break; } } /* * This was required until PhantomJS did not support Giac because * missing Float64Array support: * * if (kernel.getApplication().isScreenshotGenerator()) { return; } */ if (inputDefined) { // compute output of CAS cell and update twin GeoElement casCell.computeOutput(); } else { casCell.setUndefined(); } } /** * This might appear when we use KeepInput and display the result => we want * to show symbolic version */ @Override final public String toString(StringTemplate tpl) { // return input string, e.g. "m := c + 3" return casCell.getLabel(tpl); } /** * Returns <cellPair> tag instead of <expression> XML */ @Override protected String getExpXML(StringTemplate tpl) { return casCell.getXML(); } @Override public String getDefinition(StringTemplate tpl) { if (input == null) { return null; } if (getCasCell() != null && getCasCell().getInputVE() != null) { return getCasCell().getInputVE().toString(tpl); } return super.getDefinition(tpl); } @Override public void update() { if (doStopUpdateCascade()) { return; } // update input random numbers without label updateUnlabeledRandomGeos(); boolean hadTwinGeo = casCell.hasTwinGeo(); compute(); if (!hadTwinGeo && casCell.hasTwinGeo()) { // we got a new twin // GeoElement // reinitialize algo object setInputOutput(); // set label of the newly created twin GeoElement casCell.setLabelOfTwinGeo(); } updateDependentGeos(); } // /** // * Initializes input and output dependencies of this algorithm. // * This method can be called again after the GeoCasCell's input expression // * has changed. // */ // private void initAlgorithm() { // // old and new predecessors of casCell // TreeSet<GeoElement> oldPred = null; // // if (input != null) { // // remember old predecessors of casCell // // for updateConstructionOrder below // oldPred = casCell.getAllPredecessors(); // } // // // input // // m := c + 3 has input variable c // TreeSet<GeoElement> geoVars = casCell.getGeoElementVariables(); // GeoElement [] geos = new GeoElement[geoVars.size()]; // input = geoVars.toArray(geos); // // // // // init output // // twin geo that may be created as a side effect // // e.g. the CAS cell m := c + 3 will create a GeoNumeric m // GeoElement twinGeo = casCell.getTwinGeo(); // // setOutputLength(twinGeo == null ? 1 : 2); // setOutput(0, casCell); // if (twinGeo != null) // setOutput(1, twinGeo); // // // set dependencies // setInputOutput(); // // // Make sure that geoCasCell comes after all its predecessors // // in the construction list. // updateConstructionOrder(oldPred); // } }