/* GeoGebra - Dynamic Mathematics for Everyone http://www.geogebra.org This file is part of GeoGebra. This program is free software; you can redistribute it and/or modify it under the terms of the GNU General Public License as published by the Free Software Foundation. */ package org.geogebra.common.kernel.advanced; import org.geogebra.common.kernel.Construction; import org.geogebra.common.kernel.algos.AlgoElement; import org.geogebra.common.kernel.algos.DrawInformationAlgo; import org.geogebra.common.kernel.arithmetic.ReplaceChildrenByValues; import org.geogebra.common.kernel.commands.Commands; import org.geogebra.common.kernel.geos.CasEvaluableFunction; import org.geogebra.common.kernel.geos.GeoElement; import org.geogebra.common.kernel.geos.GeoList; import org.geogebra.common.kernel.geos.GeoNumeric; import org.geogebra.common.util.debug.Log; /** * Algorithm for the Zip[ expression of var, var, list, var, list, ... ] * command. * * @author Zbynek Konecny */ public class AlgoZip extends AlgoElement { private GeoElement expression; // input expression dependent on var private GeoElement[] vars; // input: local variable private int varCount; private int listCount; private GeoList[] over; private GeoList list; // output private int last_length = 0; private boolean expIsFunctionOrCurve, isEmpty; private AlgoElement expressionParentAlgo; // we need to check that some Object[] reference didn't cause infinite // update cycle private boolean updateRunning = false; /** * Creates a new algorithm to create a sequence of objects that form a list. * * @param cons * construction * * @param label * label for the list * @param expression * expression (first argument of zip * @param vars * variables * @param over * lists from which the variables should be taken */ public AlgoZip(Construction cons, String label, GeoElement expression, GeoElement[] vars, GeoList[] over) { this(cons, expression, vars, over); list.setLabel(label); } /** * Creates a new algorithm to create a sequence of objects that form a list. * * @param cons * construction * @param expression * expression (first argument of zip * @param vars * variables * @param over * lists from which the variables should be taken */ AlgoZip(Construction cons, GeoElement expression, GeoElement[] vars, GeoList[] over) { super(cons); this.expression = expression; this.vars = vars; this.over = over; listCount = over.length; varCount = vars.length; expressionParentAlgo = expression.getParentAlgorithm(); expIsFunctionOrCurve = expression instanceof CasEvaluableFunction; list = new GeoList(cons); setInputOutput(); // for AlgoElement compute(); } @Override public Commands getClassName() { return Commands.Zip; } // for AlgoElement @Override protected void setInputOutput() { input = new GeoElement[1 + listCount + varCount]; input[0] = expression; for (int i = 0; i < listCount; i++) { input[2 * i + 1] = vars[i]; input[2 * i + 2] = over[i]; } if (varCount > listCount) { input[listCount + varCount] = vars[varCount - 1]; } setOutputLength(1); setOutput(0, list); list.setTypeStringForXML(expression.getXMLtypeString()); setDependencies(); // done by AlgoElement } /** * Returns contents of input array excluding var (var is not input object, * but must be in input array because of GetCommandDescription method). */ @Override public GeoElement[] getInputForUpdateSetPropagation() { GeoElement[] realInput = new GeoElement[listCount + 1]; realInput[0] = expression; for (int i = 0; i < listCount; i++) { realInput[i + 1] = over[i]; } return realInput; } /** * Returns list of all contained elements. * * @return list of elements */ GeoList getList() { return list; } @Override public final void compute() { if (updateRunning) { return; } updateRunning = true; // only set undefined when some *input list* is undefined for (int i = 2; i < input.length; i += 2) { if (!input[i].isDefined()) { list.setUndefined(); updateRunning = false; return; } } list.setDefined(true); // create sequence for expression(var) by changing var according to the // given range isEmpty = minOverSize() == 0; // an update may be necessary because another variable in expression // has changed. However, the range (from, to, step) may not have // changed: // in this case it is much more efficient not to create all objects // for the list again, but just to set their new values boolean setValuesOnly = (minOverSize() == last_length); // setValues does not work for functions setValuesOnly = setValuesOnly && !expIsFunctionOrCurve; // avoid label creation, might happen e.g. in boolean oldSuppressLabels = cons.isSuppressLabelsActive(); cons.setSuppressLabelCreation(true); // update list if (setValuesOnly) { updateListItems(); } else { createNewList(); } // revert label creation setting cons.setSuppressLabelCreation(oldSuppressLabels); updateRunning = false; } private void createNewList() { // clear list if defined int i = 0; int oldListSize = list.size(); list.clear(); if (!isEmpty) { // needed capacity int n = minOverSize(); list.ensureCapacity(n); // create the sequence int currentVal = 0; while (currentVal < minOverSize()) { // check we haven't run out of memory if (kernel.getApplication().freeMemoryIsCritical()) { long mem = kernel.getApplication().freeMemory(); list.clearCache(); kernel.initUndoInfo(); // clear all undo info Log.debug("AlgoZip aborted: free memory reached " + mem); return; } // set local var value updateLocalVar(currentVal); addElement(i); currentVal += 1; i++; } } // if the old list was longer than the new one // we need to set some cached elements to undefined for (int k = oldListSize - 1; k >= i; k--) { GeoElement oldElement = list.getCached(k); oldElement.setUndefined(); oldElement.update(); } // remember current values last_length = minOverSize(); } private void addElement(int i) { // only add new objects GeoElement listElement = null; int cacheListSize = list.getCacheSize(); if (i < cacheListSize) { // we reuse existing list element from cache listElement = list.getCached(i); if (expIsFunctionOrCurve) { // for functions we always need a new element listElement.setParentAlgorithm(null); listElement.doRemove(); // replace old list element by a new one listElement = createNewListElement(); } } else { // create new list element listElement = createNewListElement(); } // copy current expression value to listElement if (!expIsFunctionOrCurve) { listElement.set(expression); if (listElement.isGeoList()) { for (int j = 0; j < varCount; j++) { ((GeoList) listElement).replaceChildrenByValues(vars[j]); } } copyDrawAlgo(listElement); } // set the value of our element listElement.update(); list.add(listElement); } private GeoElement createNewListElement() { GeoElement listElement = expression.copyInternal(cons); listElement.setParentAlgorithm(this); listElement.setConstructionDefaults(); listElement.setUseVisualDefaults(false); // functions and curves use the local variable var // so we have to replace var and all dependent objects of var // by their current values if (expIsFunctionOrCurve) { // GeoFunction if (listElement instanceof CasEvaluableFunction) { CasEvaluableFunction f = (CasEvaluableFunction) listElement; for (int i = 0; i < varCount; i++) { f.replaceChildrenByValues(vars[i]); } } } return listElement; } private void updateListItems() { if (isEmpty) { return; } int currentVal = 0; while (currentVal < minOverSize()) { GeoElement listElement = list.get(currentVal); // check we haven't run out of memory if (kernel.getApplication().freeMemoryIsCritical()) { long mem = kernel.getApplication().freeMemory(); list.clearCache(); kernel.initUndoInfo(); // clear all undo info Log.debug("AlgoZip aborted: free memory reached " + mem); return; } // set local var value updateLocalVar(currentVal); // copy expression value to listElement // if it's undefined, just copy the undefined property if (expression.isDefined()) { listElement.set(expression); if (listElement.isGeoList()) { for (int j = 0; j < varCount; j++) { ((GeoList) listElement) .replaceChildrenByValues(vars[j]); } } } else { listElement.setUndefined(); } copyDrawAlgo(listElement); listElement.update(); currentVal += 1; } } private void copyDrawAlgo(GeoElement listElement) { AlgoElement drawAlgo = expression.getDrawAlgorithm(); if (listElement instanceof GeoNumeric && drawAlgo instanceof DrawInformationAlgo) { DrawInformationAlgo algoCopy = ((DrawInformationAlgo) drawAlgo) .copy(); if (algoCopy instanceof ReplaceChildrenByValues) { for (int j = 0; j < varCount; j++) { ((ReplaceChildrenByValues) algoCopy) .replaceChildrenByValues(vars[j]); } } listElement.setDrawAlgorithm(algoCopy); listElement.setEuclidianVisible(true); } } private int minOverSize() { int min = over[0].size(); for (int i = 1; i < listCount; i++) { if (over[i].size() < min) { min = over[i].size(); } } return min; } /** * Sets value of the local loop variable of the sequence and updates all * it's dependencies until we reach the sequence algo. */ private void updateLocalVar(int index) { // set local variable to given value for (int i = 0; i < listCount; i++) { vars[i].set(over[i].get(index)); } if (varCount > listCount) { ((GeoNumeric) vars[varCount - 1]).setValue(index + 1); } // update var's algorithms until we reach expression if (expressionParentAlgo != null) { // update all dependent algorithms of the local variable var this.setStopUpdateCascade(true); for (int i = 0; i < listCount; i++) { vars[i].getAlgoUpdateSet().updateAllUntil(expressionParentAlgo); } this.setStopUpdateCascade(false); expressionParentAlgo.update(); } } }