/*
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.
*/
/*
* AlgoElement.java
*
* Created on 30. August 2001, 21:36
*/
package org.geogebra.common.kernel.algos;
import java.util.ArrayList;
import java.util.Iterator;
import java.util.List;
import java.util.TreeSet;
import org.geogebra.common.kernel.Construction;
import org.geogebra.common.kernel.EuclidianViewCE;
import org.geogebra.common.kernel.GTemplate;
import org.geogebra.common.kernel.StringTemplate;
import org.geogebra.common.kernel.View;
import org.geogebra.common.kernel.arithmetic.Inspecting;
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.GeoFunction;
import org.geogebra.common.kernel.geos.GeoScriptAction;
import org.geogebra.common.kernel.kernelND.GeoElementND;
import org.geogebra.common.kernel.kernelND.GeoPointND;
import org.geogebra.common.plugin.GeoClass;
import org.geogebra.common.util.StringUtil;
/**
* AlgoElement is the superclass of all algorithms.
*
* @author Markus
*/
public abstract class AlgoElement extends ConstructionElement
implements EuclidianViewCE {
private static boolean tempSetLock = false;
/** input elements */
public GeoElement[] input;
/**
* list of output
*
* @deprecated use setOutputLength(), setOutput(), getOutputLength(),
* getOutput() instead
*/
@Deprecated
private GeoElement[] output;
private GeoElementND[] efficientInput;
private boolean isPrintedInXML = true;
/** flag to stop update cascade */
protected boolean stopUpdateCascade = false;
/**
* list of registered outputHandler of this AlgoElement
*/
private List<OutputHandler<?>> outputHandler;
private boolean mayHaveRandomAncestors = true;
/** string builder */
protected StringBuilder sbAE = new StringBuilder();
/**
* Creates new algorithm
*
* @param c
* construction
*/
public AlgoElement(Construction c) {
this(c, true);
}
/**
* Creates new algorithm
*
* @param c
* construction
* @param addToConstructionList
* true to add this to construction list
*/
protected AlgoElement(Construction c, boolean addToConstructionList) {
super(c);
if (addToConstructionList) {
c.addToConstructionList(this, false);
}
}
/**
* add the algo to the construction (if disabled before by
* Kernel.silentMode)
*/
public void addToConstructionList() {
cons.addToConstructionList(this, false);
}
/**
* initialize output list
*
* @param n
* Output length
*
*/
protected void setOutputLength(int n) {
output = new GeoElement[n];
}
/**
* initialize output list
*
* @param g
* only output
*
*/
protected void setOnlyOutput(GeoElementND g) {
output = new GeoElement[1];
output[0] = g.toGeoElement();
}
/**
* set output number i
*
* @param i
* index
* @param geo
* output geo
*/
protected void setOutput(int i, GeoElement geo) {
output[i] = geo;
}
/**
* sets the output to the given array
*
* @param geo
* the output to set
*/
protected void setOutput(GeoElement[] geo) {
output = geo;
}
/**
* @param i
* index
* @return output geo at position i
*/
public GeoElement getOutput(int i) {
return output[i];
}
/**
*
* @return number of outputs
*/
public int getOutputLength() {
if (output == null) {
return 0;
}
return output.length;
}
/**
* One OutputHandler has been changed, we put together the new output.
*/
protected void refreshOutput() {
Iterator<OutputHandler<?>> it = getOutputHandler().iterator();
int n = 0;
while (it.hasNext()) {
n += it.next().size();
}
output = new GeoElement[n];
it = getOutputHandler().iterator();
int i = 0;
while (it.hasNext()) {
OutputHandler<?> handler = it.next();
for (int k = 0; k < handler.size(); k++) {
output[i++] = handler.getElement(k);
}
}
}
/**
* OutputHandler can manage several different output types, each with
* increasing length. For each occurring type, you need one OutputHandler in
* the Subclass (or OutputHandler<GeoElement> if the type doesn't matter).
* <br />
* Don't use this if you are accessing output directly, or use setOutput or
* setOutputLength, because the OutputHandler changes output on it's own and
* will 'overwrite' any direct changes.
*
* @param <T>
* extends GeoElement: type of the OutputHandler
*/
public class OutputHandler<T extends GeoElement> {
private elementFactory<T> fac;
private ArrayList<T> outputList;
private String[] labels;
private String indexLabel;
/**
* use Labels for this outputs
*/
public boolean setLabels;
/** number of labels already set */
private int labelsSetLength = 0;
/**
* @param fac
* elementFactory to create new Elements of type T
*/
public OutputHandler(elementFactory<T> fac) {
this.fac = fac;
outputList = new ArrayList<T>();
if (getOutputHandler() == null) {
setOutputHandler(new ArrayList<OutputHandler<?>>());
}
getOutputHandler().add(this);
}
/**
* @param fac
* elementFactory to create new Elements of type T
* @param labels
* array of labels to use for Outputelements.
*/
public OutputHandler(elementFactory<T> fac, String[] labels) {
this(fac);
this.labels = labels;
if (labels != null) {
adjustOutputSize(labels.length);
}
}
/**
* Remove this from handler
*/
public void removeFromHandler() {
getOutputHandler().remove(this);
}
/**
* @param size
* makes room in this OutputHandler for size Objects.<br />
* if there are currently more objects than size, they become
* undefined.
*/
public void adjustOutputSize(int size) {
adjustOutputSize(size, true);
}
/**
* makes room in this OutputHandler for size Objects.<br />
* if there are currently more objects than size, they become undefined.
*
* @param size
* new size
*
* @param setDependencies
* set dependencies
*/
public void adjustOutputSize(int size, boolean setDependencies) {
if (outputList.size() < size) {
augmentOutputSize(size - outputList.size(), setDependencies);
} else {
for (int i = size; i < outputList.size(); i++) {
outputList.get(i).setUndefined();
}
}
}
/**
* increases size of output by given number
*
* @param size
* size increment
*/
public void augmentOutputSize(int size) {
augmentOutputSize(size, true);
}
/**
* increases size of output to given size
*
* @param increment
* new size
* @param setDependencies
* true to set dependencies right away
*/
public void augmentOutputSize(int increment, boolean setDependencies) {
int size = increment + outputList.size();
outputList.ensureCapacity(size);
for (int i = outputList.size(); i < size; i++) {
T newGeo = fac.newElement();
outputList.add(newGeo);
if (setDependencies) {
setOutputDependencies(newGeo);
}
}
refreshOutput();
if (setLabels) {
updateLabels();
}
}
/**
* add the geos list to the output
*
* @param geos
* geos to be added
* @param setDependencies
* says if the dependencies have to be set for this output
* @param refresh
* if true, output array is recomputed using outputhandler
*/
public void addOutput(T[] geos, boolean setDependencies,
boolean refresh) {
for (int i = 0; i < geos.length; i++) {
addOutput(geos[i], setDependencies);
}
if (refresh) {
refreshOutput();
}
}
/**
* @param geo
* geo to be added
* @param setDependencies
* true to set dependencies of given geo now
*/
public void addOutput(T geo, boolean setDependencies) {
outputList.add(geo);
if (setDependencies) {
setOutputDependencies(geo);
}
}
/**
* set setLabels to true
*
* @param labels
* use this Strings as labels. If labels == null, default
* labels are used
*/
public void setLabels(String[] labels) {
this.labels = labels;
// setLabels=true;
setLabels = !cons.isSuppressLabelsActive();
if (labels != null) {
if (labels.length == 1) {
setIndexLabels(labels[0]);
}
adjustOutputSize(labels.length);
} else {
updateLabels();
}
}
/**
* set setLabels to true
*
* @param label
* use this String as indexed labels.
*/
public void setIndexLabels(String label) {
this.indexLabel = label;
// setLabels=true;
setLabels = !cons.isSuppressLabelsActive();
updateLabels();
}
/**
* assigns Labels to unlabeled elements
*/
public void updateLabels() {
for (int i = 0; i < outputList.size(); i++) {
if (!outputList.get(i).isLabelSet()) {
if (indexLabel != null) { // use indexed label
outputList.get(i).setLabel(
outputList.get(i).getIndexLabel(indexLabel));
} else if ((labels != null) && (i < labels.length)) {
outputList.get(i).setLabel(labels[i]);
} else {
outputList.get(i).setLabel(null);
}
}
}
}
/**
* set all geos undefined
*/
public void setUndefined() {
for (int i = 0; i < outputList.size(); i++) {
outputList.get(i).setUndefined();
}
}
/**
* call update for each geo
*/
public void update() {
for (int i = 0; i < outputList.size(); i++) {
outputList.get(i).update();
}
}
/**
* call update for each geo parent algo
*/
public void updateParentAlgorithm() {
for (int i = 0; i < outputList.size(); i++) {
outputList.get(i).getParentAlgorithm().update();
}
}
/**
* set the label to the next geo with no label (or create new one)
*
* @param label
* label
* @return corresponding geo
*/
public T addLabel(String label) {
T geo;
if (labelsSetLength < outputList.size()) {
geo = getElement(labelsSetLength);
// Application.debug(label+", geo="+geo);
} else {
geo = fac.newElement();
outputList.add(geo);
setOutputDependencies(geo);
refreshOutput();
}
labelsSetLength++;
geo.setLabel(label);
return geo;
}
/**
* Returns output element at given position
*
* @param i
* position (starting with 0)
* @return get the i<sup>th</sup> Element of this OutputHandler
*/
public T getElement(int i) {
return outputList.get(i);
}
/**
* @param a
* type of the Output
* @return content of this OutputHandler as array
*/
public T[] getOutput(T[] a) {
// Application.debug("getOutput:
// "+Arrays.deepToString(outputList.toArray())+"
// length:"+outputList.toArray().length);
return outputList.toArray(a);
}
/**
* @return size of the content of this OutputHandler
*/
public int size() {
return outputList.size();
}
/**
* @param labels2
* output labels
*/
public void setLabelsMulti(String[] labels2) {
// if only one label (e.g. "A") for more than one output, new labels
// will be A_1, A_2, ...
if (labels2 != null && labels2.length == 1 &&
// outputPoints.size() > 1 &&
labels2[0] != null && !labels2[0].equals("")) {
this.setIndexLabels(labels2[0]);
} else {
this.setLabels(labels2);
this.setIndexLabels(getElement(0).getLabelSimple());
}
}
}
/**
* Produces objects of type <S>
*
* @param <S>
* element type
*/
public interface elementFactory<S extends GeoElement> {
/**
* this is called by the OutputHandler every Time a new Element is
* needed.
*
* @return a new Element of type S. (e.g. new GeoPoint(cons))
*/
public S newElement();
}
/*
* needed so that JavaScript commands work:
* ggbApplet.getCommandString(objName); ggbApplet.getValueString(objName);
*/
/**
* Converts algorithm identifier into command name
*
* @param classname
* algorithm identifier
* @return internal command name
*/
final static String getCommandString(GetCommand classname) {
// init rbalgo2command if needed
// for translation of Algo-classname to command name
if (classname == null) {
return "";
}
// translate algorithm class name to internal command name
return classname.getCommand();
}
/**
* in setInputOutput() the member vars input and output are set
*/
abstract protected void setInputOutput();
/**
* in compute() the output is derived from the input
*/
public abstract void compute();
/**
* Inits this algorithm for the near-to-relationship. This is important to
* init the intersection algorithms when loading a file, so that they have a
* look at the current location of their output points.
*/
public void initForNearToRelationship() {
// overriden in subclasses
}
/**
* @return whether this algo has NEAR-TO relations (ie ambiguous output =>
* we pick the nearest possibility to last output)
*/
public boolean isNearToAlgorithm() {
return false;
}
// public static double startTime, endTime;
// public static double computeTime, updateTime;
// public static double counter;
@Override
public void update() {
if (stopUpdateCascade) {
return;
}
updateUnlabeledRandomGeos();
// counter++;
// startTime = System.currentTimeMillis();
// compute output from input
compute();
// endTime = System.currentTimeMillis();
// computeTime += (endTime - startTime);
// startTime = System.currentTimeMillis();
updateDependentGeos();
// endTime = System.currentTimeMillis();
// updateTime += (endTime - startTime );
}
/**
* update input random numbers without label
*
* @return whether something was updated
*/
public boolean updateUnlabeledRandomGeos() {
if (!mayHaveRandomAncestors || input == null) {
return false;
}
boolean ret = false;
for (int i = 0; i < input.length; i++) {
if (!input[i].isLabelSet()) {
if (input[i].getParentAlgorithm() != null) {
// if Mod[RandomBetween[1,3],2], we must go deeper and
// update
// if just RandomBetween[1,3] we just update
if (input[i].getParentAlgorithm()
.updateUnlabeledRandomGeos()
|| input[i].isRandomGeo()) {
input[i].getParentAlgorithm().compute();
ret = true;
}
} else {
// random() has no parent algo
if (input[i].isRandomGeo()) {
input[i].updateRandomGeo();
ret = true;
}
}
}
}
if (!ret) {
this.mayHaveRandomAncestors = false;
}
return ret;
}
/**
* update output geos
*/
protected void updateDependentGeos() {
// update dependent objects
for (int i = 0; i < getOutputLength(); i++) {
getOutput(i).update();
}
}
/**
* Updates all AlgoElements in the given ArrayList. Note: this method is
* more efficient than calling updateCascade() for all individual
* AlgoElements.
*
* @param algos
* list of algos that need updating
*/
public static void updateCascadeAlgos(ArrayList<AlgoElement> algos) {
if (algos == null) {
return;
}
int size = algos.size();
if (size == 0) {
return;
}
ArrayList<GeoElement> geos = new ArrayList<GeoElement>();
for (int i = 0; i < size; i++) {
AlgoElement algo = algos.get(i);
algo.compute();
for (int j = 0; j < algo.getOutputLength(); j++) {
algo.getOutput(j).update();
geos.add(algo.getOutput(j));
}
}
// update all geos
tempSetLock = true;
GeoElement.updateCascade(geos, getTempSet(), true);
tempSetLock = false;
}
private static TreeSet<AlgoElement> tempSet;
private static TreeSet<AlgoElement> getTempSet() {
if (tempSet == null || tempSetLock) {
tempSet = new TreeSet<AlgoElement>();
}
return tempSet;
}
// public part
/**
* @return list of output
*/
public GeoElement[] getOutput() {
return output;
}
/**
* @return array of input elements
*/
final public GeoElement[] getInput() {
return input;
}
/**
* Note : maybe overridden for xOy plane additionnal input
*
* @param i
* index
* @return i-th input
*/
public GeoElementND getInput(int i) {
return input[i];
}
/**
* @return input without local variables
*/
public GeoElement[] getInputForUpdateSetPropagation() {
return input;
}
/**
* DEPENDENCY handling the dependencies are treated here by using input and
* output which must be set by every algorithm. Note: setDependencies() is
* called by every algorithm in topological order (i.e. possible helper
* algos call this method before the using algo does).
*
* @see #setInputOutput()
*/
final protected void setDependencies() {
// dependents on input
for (int i = 0; i < input.length; i++) {
input[i].addAlgorithm(this);
}
doSetDependencies();
}
/**
* Adds this algorithm to the update set of all inputs without adding input
* dependencies.
*/
final protected void setDependenciesOutputOnly() {
for (int i = 0; i < input.length; i++) {
input[i].addToUpdateSetOnly(this);
}
doSetDependencies();
}
/**
* Set output dependencies and add to cons list
*/
protected void doSetDependencies() {
this.mayHaveRandomAncestors = true;
setOutputDependencies();
cons.addToAlgorithmList(this);
}
/**
* @param standardInput
* input
* @param efficientInput
* input without moving point for locus
*/
protected final void setEfficientDependencies(GeoElement[] standardInput,
GeoElementND[] efficientInput) {
// dependens on standardInput
for (int i = 0; i < standardInput.length; i++) {
standardInput[i].addToAlgorithmListOnly(this);
}
// we use efficientInput for updating
for (int i = 0; i < efficientInput.length; i++) {
efficientInput[i].addToUpdateSetOnly(this);
}
// input is standardInput
input = standardInput;
this.efficientInput = efficientInput;
doSetDependencies();
}
private void setOutputDependencies() {
// parent algorithm of output
for (int i = 0; i < getOutputLength(); i++) {
setOutputDependencies(getOutput(i));
}
}
/**
* sets the given geo to be child of this algo
*
* @param output
* output element
*/
protected void setOutputDependencies(GeoElement output) {
// parent algorithm of output
output.setParentAlgorithm(this);
// every algorithm with an image as output
// should be notified about view changes
if (output.isGeoImage()) {
cons.registerEuclidianViewCE(this);
}
// make sure that every output has same construction as this algorithm
// this is important for macro constructions that have input geos from
// outside the macro: the output should be part of the macro
// construction!
if (cons != output.cons) {
output.setConstruction(cons);
}
}
@Override
public boolean euclidianViewUpdate() {
update();
return false;
}
/** flag stating whether remove() on this algo was already called */
protected boolean removed = false;
/**
* delete dependent objects
*/
protected void removeOutput() {
for (int i = 0; i < getOutputLength(); i++) {
getOutput(i).doRemove();
}
}
/**
* Removes algorithm and all dependent objects from construction.
*/
@Override
public void remove() {
if (removed) {
return;
}
removed = true;
cons.removeFromConstructionList(this);
cons.removeFromAlgorithmList(this);
// delete dependent objects
removeOutput();
// delete from algorithm lists of input
for (int i = 0; i < input.length; i++) {
if (!isProtectedInput() && input[i].canBeRemovedAsInput()
&& !input[i].isLabelSet() && !input[i].isGeoCasCell()) {
input[i].remove();
}
input[i].removeAlgorithm(this);
}
// delete from algorithm lists of efficient input
if (efficientInput != null) {
for (int i = 0; i < efficientInput.length; i++) {
efficientInput[i].removeAlgorithm(this);
}
}
}
private boolean protectedInput = false;
/**
* sets if the "not labeled" inputs are protected from remove
*
* @param flag
* flag
*/
public void setProtectedInput(boolean flag) {
protectedInput = flag;
}
/**
* Tells this algorithm to react on the deletion of one of its outputs.
*
* @param out
* output to be removed
*/
public void remove(GeoElement out) {
remove();
}
/**
* Calls doRemove() for all output objects of this algorithm except for
* keepGeo.
*
* @param keepGeo
* geo to be kept
*/
public void removeOutputExcept(GeoElement keepGeo) {
for (int i = 0; i < getOutputLength(); i++) {
GeoElement geo = getOutput(i);
if (geo != keepGeo) {
geo.doRemove();
}
}
}
/**
* Tells all views to add all output GeoElements of this algorithm.
*/
@Override
final public void notifyAdd() {
for (int i = 0; i < getOutputLength(); ++i) {
getOutput(i).notifyAdd();
}
}
/**
* Tells all views to remove all output GeoElements of this algorithm.
*/
@Override
final public void notifyRemove() {
for (int i = 0; i < getOutputLength(); ++i) {
getOutput(i).notifyRemove();
}
}
@Override
final public GeoElementND[] getGeoElements() {
return getOutput();
}
/**
* Returns whether all output objects have the same type.
*
* @return whether all outputs have the same type
*/
final public boolean hasSingleOutputType() {
GeoClass type = getOutput(0).getGeoClassType();
for (int i = 1; i < getOutputLength(); ++i) {
if (getOutput(i).getGeoClassType() != type) {
return false;
}
}
return true;
}
@Override
final public boolean isAlgoElement() {
return true;
}
@Override
final public boolean isGeoElement() {
return false;
}
/**
* Returns true iff one of the output geos is shown in the construction
* protocol
*/
@Override
final public boolean isConsProtocolBreakpoint() {
for (int i = 0; i < getOutputLength(); i++) {
if (getOutput(i).isConsProtocolBreakpoint()) {
return true;
}
}
return false;
}
/**
* Compares using getConstructionIndex() to order algos in update order.
* Note: 0 is only returned for this == obj.
*
* @overwrite ConstructionElement.compareTo()
*/
@Override
public int compareTo(ConstructionElement obj) {
if (this == obj) {
return 0;
}
ConstructionElement ce = obj;
int thisIndex = getConstructionIndex();
int objIndex = ce.getConstructionIndex();
if (thisIndex == objIndex) {
// two help algorithms can have same construction index
// if they have same parent geos, see #2693
// in this case we use the creation ID to distinguish them
return super.compareTo(obj);
} else if (thisIndex < objIndex) {
return -1;
}
return 1;
}
/**
* Returns construction index in current construction. For an algorithm that
* is not in the construction list, the largest construction index of its
* inputs is returned.
*/
@Override
public int getConstructionIndex() {
int index = super.getConstructionIndex();
// algorithm is in construction list
if (index >= 0) {
return index;
}
// algorithm is not in construction list
for (int i = 0; i < input.length; i++) {
int temp = input[i].getConstructionIndex();
if (temp > index) {
index = temp;
}
}
return index;
}
/**
* Returns the smallest possible construction index for this object in its
* construction.
*/
@Override
public int getMinConstructionIndex() {
// index must be greater than every input's index
int max = 0;
for (int i = 0; i < input.length; ++i) {
int index = input[i].getConstructionIndex();
if (index > max) {
max = index;
}
}
return max + 1;
}
/**
* Returns the largest possible construction index for this object in its
* construction.
*/
@Override
public int getMaxConstructionIndex() {
// index is less than minimum of all dependent algorithm's index of all
// output
ArrayList<AlgoElement> algoList;
int size, index;
int min = cons.steps();
for (int k = 0; k < getOutputLength(); ++k) {
algoList = getOutput(k).getAlgorithmList();
size = algoList.size();
for (int i = 0; i < size; ++i) {
index = algoList.get(i).getConstructionIndex();
if (index < min) {
min = index;
}
}
}
return min - 1;
}
/**
* adds all predecessors of this object to the given list the set is kept
* topologically sorted
*
* @param set
* set of geos to be added
* @param onlyIndependent
* whether only indpendent geos should be added
*/
@Override
public final void addPredecessorsToSet(TreeSet<GeoElement> set,
boolean onlyIndependent) {
for (int i = 0; i < input.length; i++) {
GeoElement parent = input[i];
if (!set.contains(parent)) {
if (!onlyIndependent) {
set.add(parent);
}
parent.addPredecessorsToSet(set, onlyIndependent);
}
}
}
/**
* @param set
* predecessors set
* @param check
* filter for adding predecessors
*/
public final void addPredecessorsToSet(TreeSet<GeoElement> set,
Inspecting check) {
for (int i = 0; i < input.length; i++) {
GeoElement parent = input[i];
if (!set.contains(parent)) {
if (check.check(parent)) {
set.add(parent);
}
parent.addPredecessorsToSet(set, check);
}
}
}
/**
* @param set
* set of randomizable predecessors
*/
public final void addRandomizablePredecessorsToSet(
TreeSet<GeoElement> set) {
for (int i = 0; i < input.length; i++) {
GeoElement parent = input[i];
if (!set.contains(parent)) {
parent.addRandomizablePredecessorsToSet(set);
}
}
}
/**
* Returns all moveable input points of this algorithm.
*
* @return list of moveable input points
*/
public ArrayList<GeoPointND> getFreeInputPoints() {
if (this instanceof AlgoLocusStroke) {
return new ArrayList<GeoPointND>(0);
}
if (freeInputPoints == null || (this instanceof AlgoPolyLine
&& ((AlgoPolyLine) this).getIsPenStroke())) {
freeInputPoints = new ArrayList<GeoPointND>(input.length);
// don't use free points from dependent algos with expression trees
if (!(this instanceof DependentAlgo)) {
for (int i = 0; i < input.length; i++) {
if (input[i].isGeoPoint() && input[i].isIndependent()) {
freeInputPoints.add((GeoPointND) input[i]);
}
}
}
}
return freeInputPoints;
}
private ArrayList<GeoPointND> freeInputPoints;
/**
* Returns all input points of this algorithm.
*
* @return list of input points
*/
public ArrayList<GeoPointND> getInputPoints() {
if (inputPoints == null) {
inputPoints = new ArrayList<GeoPointND>(input.length);
for (int i = 0; i < input.length; i++) {
if (input[i].isGeoPoint()) {
inputPoints.add((GeoPointND) input[i]);
}
}
}
return inputPoints;
}
private ArrayList<GeoPointND> inputPoints;
@Override
final public boolean isIndependent() {
return false;
}
@Override
public String getNameDescription() {
sbAE.setLength(0);
if (getOutput(0).isLabelSet()) {
sbAE.append(getOutput(0).getNameDescription());
}
for (int i = 1; i < getOutputLength(); ++i) {
if (getOutput(i).isLabelSet()) {
sbAE.append("\n");
sbAE.append(getOutput(i).getNameDescription());
}
}
return sbAE.toString();
}
/**
* @param tpl
* template
* @return regression output
*/
public String getAlgebraDescriptionRegrOut(StringTemplate tpl) {
sbAE.setLength(0);
if (getOutput(0).isLabelSet()) {
sbAE.append(getOutput(0).getAlgebraDescriptionRegrOut(tpl));
}
for (int i = 1; i < getOutputLength(); ++i) {
if (getOutput(i).isLabelSet()) {
sbAE.append("\n");
sbAE.append(getOutput(i).getAlgebraDescriptionRegrOut(tpl));
}
}
return sbAE.toString();
}
@Override
public String getDefinitionDescription(StringTemplate tpl) {
return toString(tpl);
}
@Override
public String getDefinition(StringTemplate tpl) {
String def = getDefinitionName(tpl);
// command name
if ("Expression".equals(def)) {
return toString(tpl);
}
// #2706
if (input == null) {
return null;
}
sbAE.setLength(0);
if (tpl.isPrintLocalizedCommandNames()) {
sbAE.append(getLoc().getCommand(def));
} else {
sbAE.append(def);
}
int length = getInputLengthForCommandDescription();
sbAE.append(tpl.leftSquareBracket());
// input legth is 0 for ConstructionStep[]
if (length > 0) {
sbAE.append(getInput(0).getLabel(tpl));
}
for (int i = 1; i < length; ++i) {
sbAE.append(", ");
appendCheckVector(sbAE, getInput(i), tpl);
}
sbAE.append(tpl.rightSquareBracket());
return sbAE.toString();
}
/*
* see #1377 g:X = (-5, 5) + t (4, -3)
*/
private void appendCheckVector(StringBuilder sb, GeoElementND geo,
StringTemplate tpl) {
String cmd = geo.getLabel(tpl);
if (geo.isGeoVector()) {
String vectorCommand = "Vector[";
if (tpl.isPrintLocalizedCommandNames())
{
vectorCommand = getLoc().getCommand("Vector") + "["; // want it
}
// translated
// eg
// for
// redefine
// dialog
boolean needsWrapping = !geo.isLabelSet()
&& !cmd.startsWith(vectorCommand);
if (needsWrapping) {
sb.append(vectorCommand);
}
sb.append(cmd);
if (needsWrapping) {
sb.append(']');
}
} else {
sb.append(cmd);
}
}
@Override
@Deprecated
public final String toString() {
return toString(StringTemplate.defaultTemplate);
}
/**
* translate class name to internal command name GeoGebra File Format
*
* @param tpl
* string template
*
* @return internal command name
*/
public String getDefinitionName(StringTemplate tpl) {
String cmdname;
GetCommand classname;
// get class name
classname = getClassName();
// translate algorithm class name to internal command name
cmdname = getCommandString(classname);
// dependent algorithm is an "Expression"
if (!"Expression".equals(cmdname)) {
if (tpl.isUseTempVariablePrefix()) {
// protect GeoGebra commands when sent to CAS
// e.g. Element[list, 1] becomes ggbtmpvarElement[list, 1] to
// make sure that the CAS does not evaluate this command, see
// #1447
cmdname = tpl.printVariableName(cmdname);
}
}
return cmdname;
}
/**
* Returns this algorithm and it's output objects (GeoElement) in XML
* format. GeoGebra File Format.
*/
@Override
public void getXML(boolean getlistenersToo, StringBuilder sb) {
getXML(sb, true);
}
@Override
public void getXML_OGP(StringBuilder sb) {
getXML_OGP(sb, true);
}
/**
* @return XML representation of this algo, including output objects
*/
public String getXML() {
StringBuilder sb = new StringBuilder();
getXML(sb, true);
return sb.toString();
}
/**
* Adds XML representation of this algo to the string builder
*
* @param sb
* string builder
* @param includeOutputGeos
* true to include output geos
*/
public final void getXML(StringBuilder sb, boolean includeOutputGeos) {
// this is needed for helper commands like
// intersect for single intersection points
if (!isPrintedInXML) {
return;
}
// turn off eg Arabic digits
// USE INTERNAL COMMAND NAMES IN EXPRESSION
try {
// command
StringTemplate tpl = StringTemplate.xmlTemplate;
String cmdname = getDefinitionName(StringTemplate.xmlTemplate);
if (hasExpXML(cmdname)) {
sb.append(getExpXML(tpl));
} else {
sb.append(getCmdXML(cmdname, tpl));
}
if (includeOutputGeos) {// && output != null) {
getOutputXML(sb);
}
} catch (Exception e) {
e.printStackTrace();
}
}
/**
* @param cmdName
* command name
* @return whether this algo should print <expression> in XML
*/
protected boolean hasExpXML(String cmdName) {
return "Expression".equals(cmdName);
}
/**
* Adds XML representation of this algo to the string builder OGP version
*
* @param sb
* string builder
* @param includeOutputGeos
* true to include output geos
*/
public final void getXML_OGP(StringBuilder sb, boolean includeOutputGeos) {
// this is needed for helper commands like
// intersect for single intersection points
if (!isPrintedInXML) {
return;
}
// turn off eg Arabic digits
// USE INTERNAL COMMAND NAMES IN EXPRESSION
try {
// command
StringTemplate tpl = StringTemplate.ogpTemplate;
String cmdname = getDefinitionName(tpl);
if ("Expression".equals(cmdname)) {
sb.append(getExpXML(tpl));
} else {
sb.append(getCmdXML(cmdname, tpl));
}
if (includeOutputGeos) {// && output != null) {
getOutputXML(sb);
}
} catch (Exception e) {
e.printStackTrace();
}
}
/**
* concatenate output XML to sb
*
* @param sb
* string builder
*/
protected void getOutputXML(StringBuilder sb) {
// output
GeoElement geo;
for (int i = 0; i < getOutputLength(); i++) {
geo = getOutput(i);
// save only GeoElements that have a valid label
// Application.debug(geo.toString()+"--"+geo.isLabelSet());
if (geo.isLabelSet()) {
geo.getXML(false, sb);
}
}
}
/**
* Expressions should be shown as out = expression e.g.
* <expression label="u" exp="a + 7 b"/>
*
* @param tpl
* string template
* @return expression XML tag
*/
protected String getExpXML(StringTemplate tpl) {
StringBuilder sb = new StringBuilder();
sb.append("<expression");
// add label
if (/* output != null && */getOutputLength() == 1) {
if (getOutput(0).isLabelSet()) {
sb.append(" label=\"");
StringUtil.encodeXML(sb, getOutput(0).getLabel(tpl));
sb.append("\"");
}
}
// add expression
sb.append(" exp=\"");
StringUtil.encodeXML(sb, toExpString(tpl));
sb.append("\"");
// make sure that a vector remains a vector and a point remains a point
if (getOutputLength() > 0)// (output != null)
{
if (getOutput(0).isGeoPoint()) {
sb.append(" type=\"point\"");
} else if (getOutput(0).isGeoVector()) {
sb.append(" type=\"vector\"");
} else if (getOutput(0).isGeoLine()) {
sb.append(" type=\"line\"");
} else if (getOutput(0).isGeoPlane()) {
sb.append(" type=\"plane\"");
} else if (getOutput(0).isGeoConic()) {
sb.append(" type=\"conic\"");
} else if (getOutput(0).isGeoQuadric()) {
sb.append(" type=\"quadric\"");
} else if (getOutput(0).isGeoImplicitPoly()) {
sb.append(" type=\"implicitPoly\"");
}
}
// expression
sb.append(" />\n");
return sb.toString();
}
// standard command has cmdname, output, input
private String getCmdXML(String cmdname, StringTemplate tpl) {
StringBuilder sb = new StringBuilder();
if (getOutputLength() > 0 && getOutput(0) instanceof GeoScriptAction) {
return "";
}
sb.append("<command name=\"");
if ("".equals(cmdname)) {
sb.append("AlgoNonCommand"); // In such cases we may want to add a
// Command for the Algo
} else {
sb.append(cmdname);
}
sb.append("\"");
if (!"".equals(cmdname) && (this instanceof AlgoListElement
|| this.getClassName().equals(Commands.Cell)
|| this.getClassName().equals(Commands.Object))) {
// need to write the geo type in the XML if it's undefined
// so that it's the same type when the file is loaded again
sb.append(" type=\"");
sb.append(getOutput()[0].getXMLtypeString());
sb.append("\"");
}
if (getOutputLength() > 0 && getOutput(0) instanceof GeoFunction) {
// need to write the geo type in the XML if it's undefined
// so that it's the same type when the file is loaded again
String varStr = ((GeoFunction) getOutput(0))
.getVarString(StringTemplate.defaultTemplate);
if (!"x".equals(varStr)) {
sb.append(" var=\"");
sb.append(varStr);
sb.append("\"");
}
}
sb.append(">\n");
if (getInputLength() > 0 && getInput(0) instanceof CasEvaluableFunction
&& !getInput(0).isLabelSet()) {
((CasEvaluableFunction) getInput(0)).printCASEvalMapXML(sb);
}
// add input information
if (input != null) {
sb.append("\t<input");
for (int i = 0; i < getInputLengthForXML(); i++) {
sb.append(" a");
sb.append(i);
// attribute name is input No.
sb.append("=\"");
GeoElementND inputGeo = getInput(i);
String cmd = StringUtil.encodeXML(inputGeo.getLabel(tpl));
// ensure a vector stays a vector!
// eg g:X = (-5, 5) + t (4, -3)
if (inputGeo.isGeoVector() && !inputGeo.isLabelSet()
&& !cmd.startsWith("Vector[")) {
// add Vector[ ] command around argument
// to make sure that this really becomes a vector again
// eg g:X = (-5, 5) + t (4, -3)
sb.append("Vector["); // in XML, so don't want this
// translated
sb.append(cmd);
sb.append("]");
} else {
// standard case
sb.append(cmd);
}
sb.append("\"");
}
sb.append("/>\n");
}
// add output information
if (getOutputLength() > 0) {
getCmdOutputXML(sb, tpl);
}
sb.append("</command>\n");
return sb.toString();
}
/**
*
* @return input length
*/
final public int getInputLength() {
return input == null ? 0 : input.length;
}
/**
*
* @return input length for XML (undo/redo/replace)
*/
protected int getInputLengthForXML() {
return getInputLength();
}
/**
*
* @return input length for command description
*/
protected int getInputLengthForCommandDescription() {
return getInputLength();
}
/**
*
* @return input length for XML (undo/redo), maybe +1 for xOy plane
*/
final protected int getInputLengthForXMLMayNeedXOYPlane() {
if (!cons.isGettingXMLForReplace() || kernel.getXOYPlane() == null) { // saving
// mode,
// or
// 2D
return getInputLength();
}
return getInputLength() + 1; // add "xOyPlane"
}
/**
*
* @return input length for command description, maybe +1 for xOy plane
*/
final protected int getInputLengthForCommandDescriptionMayNeedXOYPlane() {
if (kernel.isSaving() || kernel.noNeedToSpecifyXOYPlane()) { // saving
// mode,
// or 2D
return getInputLength();
}
return getInputLength() + 1; // add "xOyPlane"
}
/**
*
* @param i
* index
* @return input or xOy plane if i == input length
*/
final protected GeoElementND getInputMaybeXOYPlane(int i) {
if (i == getInputLength()) {
return kernel.getXOYPlane();
}
return input[i];
}
/**
* get XML for command output
*
* @param sb
* current string builder
* @param tpl
* template for string
*/
protected void getCmdOutputXML(StringBuilder sb, StringTemplate tpl) {
sb.append("\t<output");
for (int i = 0; i < getOutputLength(); i++) {
sb.append(" a");
sb.append(i);
// attribute name is output No.
sb.append("=\"");
GeoElement geo = getOutputForCmdXML(i);
if (geo.isLabelSet()) {
StringUtil.encodeXML(sb, geo.getLabel(tpl));
}
sb.append("\"");
}
sb.append("/>\n");
}
/**
* @param i
* index
* @return output geo at position i for command output XML
*/
protected GeoElement getOutputForCmdXML(int i) {
return getOutput(i);
}
/**
* @return class identificator (may not be unique)
*/
public abstract GetCommand getClassName();
/**
* Sets whether the output of this command should be labeled. This setting
* is used for getXML().
*
* @param flag
* whether the output of this command should be labeled
*/
public void setPrintedInXML(boolean flag) {
isPrintedInXML = flag;
if (flag) {
cons.addToConstructionList(this, true);
} else {
cons.removeFromConstructionList(this);
}
}
/**
* @return whether the output of this command should be labeled (for GetXML)
*/
protected boolean isPrintedInXML() {
return isPrintedInXML;
}
@Override
public String toString(StringTemplate tpl) {
return getDefinition(tpl);
}
/**
* May differ from toString where LHS is needed
*
* @param tpl
* string template
* @return expression string
*/
protected String toExpString(StringTemplate tpl) {
return toString(tpl);
}
/**
* @return whether this algo should stop updateCascade calls
*/
protected final boolean doStopUpdateCascade() {
return stopUpdateCascade;
}
/**
* @param stopUpdateCascade
* whether this algo should stop updateCascade calls
*/
protected final void setStopUpdateCascade(boolean stopUpdateCascade) {
this.stopUpdateCascade = stopUpdateCascade;
}
/**
* @return whether this needs to be updated when stepping through cons
*/
public boolean wantsConstructionProtocolUpdate() {
return false;
}
/**
* Makes sure that this algorithm will be updated after the given algorithm.
*
* @param updateAfterAlgo
* algo after which this should be updated
* @see #getUpdateAfterAlgo()
*/
final public void setUpdateAfterAlgo(AlgoElement updateAfterAlgo) {
this.updateAfterAlgo = updateAfterAlgo;
}
private AlgoElement updateAfterAlgo;
/**
* Returns the algorithm the should be updated right before this algorithm.
* This is being used in AlgorithmSet to sort algorithms by updating order.
*
* @return null when there is no special algorithm that needs to be updated
* first
* @see #getUpdateAfterAlgo()
*/
final public AlgoElement getUpdateAfterAlgo() {
return updateAfterAlgo;
}
// /////////////////////////////////////////
// USED FOR PREVIEWABLES
// /////////////////////////////////////////
/**
* remove all outputs from algebra view
*/
public void removeOutputFromAlgebraView() {
View av = kernel.getApplication().getAlgebraView();
if (av != null) {
for (int i = 0; i < getOutputLength(); i++) {
av.remove(getOutput(i));
}
}
}
/**
* remove all outputs from picking
*/
public void removeOutputFromPicking() {
for (int i = 0; i < getOutputLength(); i++) {
getOutput(i).setIsPickable(false);
}
}
/**
* @return the outputHandler
*/
public List<OutputHandler<?>> getOutputHandler() {
return outputHandler;
}
/**
* @param outputHandler
* the outputHandler to set
*/
public void setOutputHandler(List<OutputHandler<?>> outputHandler) {
this.outputHandler = outputHandler;
}
/**
* @return true for latex commands
*/
public boolean isLaTeXTextCommand() {
return false;
}
/**
* Check for undefined inputs, may be overridden
*
* @return whether all relevant inputs are defined
*/
public boolean isUndefined() {
for (GeoElement geo : getInput()) {
if (!geo.isDefined()) {
return true;
}
}
return false;
}
@Override
public String toString(GTemplate tpl) {
return toString(tpl.getTemplate());
}
/**
* @return whether input is protected from deletion
*/
public boolean isProtectedInput() {
return protectedInput;
}
}