/*
* JBoss, Home of Professional Open Source.
* See the COPYRIGHT.txt file distributed with this work for information
* regarding copyright ownership. Some portions may be licensed
* to Red Hat, Inc. under one or more contributor license agreements.
*
* This library 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.
*
* This library 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 this library; if not, write to the Free Software
* Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA
* 02110-1301 USA.
*/
package org.teiid.query.processor.proc;
import java.util.ArrayList;
import java.util.List;
import org.teiid.client.plan.PlanNode;
import org.teiid.query.sql.proc.Statement.Labeled;
import org.teiid.query.tempdata.TempTableStore;
import org.teiid.query.tempdata.TempTableStore.TransactionMode;
/**
* A program is a sequence of {@link ProgramInstruction ProgramInstruction}. Certain
* ProgramInstructions, such as {@link IfInstruction} and {@link WhileInstruction} may
* have pointers to sub programs.
*/
public class Program implements Cloneable, Labeled {
private List<ProgramInstruction> programInstructions;
private int counter = 0;
private boolean atomic;
private String label;
private TempTableStore tempTables;
private boolean startedTxn;
private String exceptionGroup;
private Program exceptionProgram;
private boolean trappingExceptions = false;
/**
* Constructor for Program.
*/
public Program(boolean atomic) {
this.atomic = atomic;
}
public void setStartedTxn(boolean startedTxn) {
this.startedTxn = startedTxn;
}
public boolean startedTxn() {
return startedTxn;
}
@Override
public String getLabel() {
return label;
}
@Override
public void setLabel(String label) {
this.label = label;
}
public boolean isAtomic() {
return atomic;
}
public TempTableStore getTempTableStore() {
return tempTables;
}
/**
* Returns the next instruction to be executed, or null if there are
* none or no more instructions.
* @return ProgramInstruction to be executed next, or null if there
* are no more to execute (or if this Program is empty)
*/
public ProgramInstruction getCurrentInstruction(){
return getInstructionAtIndex(counter);
}
/**
* Increments the program counter, so that the next call to
* {@link #getCurrentInstruction} will return the following
* instruction. This method is intended to be used by a
* ProcessingInstruction itself, to control the flow of execution.
*/
public void incrementProgramCounter(){
counter++;
}
/**
* Decrements the program counter, so that the next call to
* {@link #getCurrentInstruction} will return the previous
* instruction. This method is intended to be used by a
* ProcessingInstruction itself, to control the flow of execution.
*/
public void decrementProgramCounter(){
counter--;
}
/**
* Resets this program, so it can be run through again.
*/
public void reset(String sessionId){
counter = 0;
this.tempTables = new TempTableStore(sessionId, TransactionMode.ISOLATE_WRITES);
this.startedTxn = false;
this.trappingExceptions = false;
}
int getProgramCounter(){
return counter;
}
/**
* Returns the instruction to be executed at the indicated index,
* or null if there is no instruction at that index.
* @return instruction to be executed at the indicated index,
* or null if there is no instruction at that index.
*/
public ProgramInstruction getInstructionAt(int instructionIndex){
return getInstructionAtIndex(instructionIndex);
}
public void addInstruction(ProgramInstruction instruction){
if (instruction != null){
getProcessorInstructions().add(instruction);
}
}
public void addInstructions(Program instructions){
if (instructions != null){
getProcessorInstructions().addAll(instructions.getProcessorInstructions());
}
}
/**
* Produces a deep clone.
*/
public Program clone(){
Program program = new Program(atomic);
program.counter = this.counter;
if (this.programInstructions != null){
ArrayList<ProgramInstruction> clonedInstructions = new ArrayList<ProgramInstruction>(this.programInstructions.size());
for (ProgramInstruction pi : this.programInstructions) {
clonedInstructions.add( pi.clone() );
}
program.programInstructions = clonedInstructions;
}
program.label = label;
program.exceptionGroup = this.exceptionGroup;
if (this.exceptionProgram != null) {
program.exceptionProgram = this.exceptionProgram.clone();
}
return program;
}
public PlanNode getDescriptionProperties() {
PlanNode props = new PlanNode("Program"); //$NON-NLS-1$
if (label != null) {
props.addProperty("Label", label); //$NON-NLS-1$
}
if(this.programInstructions != null) {
for (int i = 0; i < programInstructions.size(); i++) {
ProgramInstruction inst = programInstructions.get(i);
PlanNode childProps = inst.getDescriptionProperties();
props.addProperty("Instruction " + i, childProps); //$NON-NLS-1$
}
}
if (this.exceptionGroup != null) {
props.addProperty("EXCEPTION GROUP", this.exceptionGroup); //$NON-NLS-1$
if (this.exceptionProgram != null) {
props.addProperty("EXCEPTION HANDLER", this.exceptionProgram.getDescriptionProperties()); //$NON-NLS-1$
}
}
return props;
}
//=========================================================================
//UTILITY
//=========================================================================
/**
* Returns the instruction to be executed at the indicated index,
* or null if there is no instruction at that index.
* @return instruction to be executed at the indicated index,
* or null if there is no instruction at that index.
*/
private ProgramInstruction getInstructionAtIndex(int instructionIndex){
if (programInstructions != null){
if (instructionIndex < getProcessorInstructions().size()){
return getProcessorInstructions().get(instructionIndex);
}
}
return null;
}
public List<ProgramInstruction> getProcessorInstructions(){
if (programInstructions == null){
programInstructions = new ArrayList<ProgramInstruction>();
}
return programInstructions;
}
public String toString() {
StringBuilder str = new StringBuilder();
programToString(str);
if (exceptionGroup != null) {
str.append("\nEXCEPTION ").append(exceptionGroup).append("\n"); //$NON-NLS-1$ //$NON-NLS-2$
}
if (exceptionProgram != null) {
exceptionProgram.programToString(str);
}
return "PROGRAM counter " + this.counter + "\n" + str.toString(); //$NON-NLS-1$ //$NON-NLS-2$
}
/**
* This method calls itself recursively if either a While or If instruction is encountered.
* The sub program(s) from those kinds of instructions are passed, recursively, into this
* method.
*/
private final void programToString(StringBuilder str) {
int instructionIndex = 0;
ProgramInstruction inst = getInstructionAt(instructionIndex);
while(inst != null) {
printLine(instructionIndex++, inst.toString(), str);
if(instructionIndex > 1000) {
printLine(instructionIndex, "[OUTPUT TRUNCATED...]", str); //$NON-NLS-1$
break;
}
inst = getInstructionAt(instructionIndex);
}
}
private static final void printLine(int counter, String line, StringBuilder buffer) {
// Pad counter with spaces
String counterStr = "" + counter + ": "; //$NON-NLS-1$ //$NON-NLS-2$
if(counter < 10) {
counterStr += " "; //$NON-NLS-1$
}
if(counterStr.length() == 1) {
counterStr += " "; //$NON-NLS-1$
} else if(counterStr.length() == 2) {
counterStr += " "; //$NON-NLS-1$
}
buffer.append(counterStr + line + "\n"); //$NON-NLS-1$
}
public void setExceptionGroup(String exceptionGroup) {
this.exceptionGroup = exceptionGroup;
}
public void setExceptionProgram(Program exceptionBlock) {
this.exceptionProgram = exceptionBlock;
}
public String getExceptionGroup() {
return exceptionGroup;
}
public Program getExceptionProgram() {
return exceptionProgram;
}
public boolean isTrappingExceptions() {
return trappingExceptions;
}
public void setTrappingExceptions(boolean trappingExceptions) {
this.trappingExceptions = trappingExceptions;
}
public Boolean requiresTransaction(boolean transactionalReads) {
if (!transactionalReads && this.isAtomic()) {
return false;
}
return instructionsRequireTransaction(transactionalReads);
}
public Boolean instructionsRequireTransaction(boolean transactionalReads) {
boolean possiblyRequired = false;
boolean last = false;
for (ProgramInstruction instruction : this.programInstructions) {
Boolean instructionRequires = instruction.requiresTransaction(transactionalReads);
if (instructionRequires == null) {
if (possiblyRequired) {
return true;
}
possiblyRequired = true;
last = true;
continue;
}
last = false;
if (instructionRequires) {
return true;
}
}
if (possiblyRequired) {
if (!last) {
//we'd have to test more in depth about whether the later statements could fail
return true;
}
return null;
}
return false;
}
}