/*
 * OpenProdoc
 * 
 * See the help doc files distributed with
 * this work for additional information regarding copyright ownership.
 * Joaquin Hierro licenses this file to You under:
 * 
 * License GNU Affero GPL v3 http://www.gnu.org/licenses/agpl.html
 * 
 * you may not use this file except in compliance with the License.  
 * Unless agreed to in writing, software is distributed on an "AS IS" BASIS,
 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 * See the License for the specific language governing permissions and
 * limitations under the License.
 * 
 * author: Joaquin Hierro      2011
 * 
 */

package prodoc;

import java.math.BigDecimal;
import java.text.DecimalFormat;
import java.text.ParseException;
import java.text.SimpleDateFormat;
import java.util.ArrayList;
import java.util.Date;
import java.util.HashMap;
import java.util.HashSet;
import java.util.Iterator;
import java.util.List;
import java.util.Vector;
import org.w3c.dom.Node;
import org.w3c.dom.NodeList;
import net.sf.jsqlparser.expression.BinaryExpression;
import net.sf.jsqlparser.expression.Expression;
import net.sf.jsqlparser.expression.Function;
import net.sf.jsqlparser.expression.NotExpression;
import net.sf.jsqlparser.expression.Parenthesis;
import net.sf.jsqlparser.expression.StringValue;
import net.sf.jsqlparser.expression.operators.conditional.AndExpression;
import net.sf.jsqlparser.expression.operators.conditional.OrExpression;
import net.sf.jsqlparser.expression.operators.relational.ComparisonOperator;
import net.sf.jsqlparser.expression.operators.relational.ExpressionList;
import net.sf.jsqlparser.expression.operators.relational.InExpression;
import net.sf.jsqlparser.parser.CCJSqlParserUtil;
import net.sf.jsqlparser.statement.select.AllColumns;
import net.sf.jsqlparser.statement.select.OrderByElement;
import net.sf.jsqlparser.statement.select.PlainSelect;
import net.sf.jsqlparser.statement.select.Select;
import net.sf.jsqlparser.statement.select.SelectExpressionItem;
import net.sf.jsqlparser.statement.select.SelectItem;
import net.sf.jsqlparser.util.TablesNamesFinder;
import static prodoc.Attribute.DECIMALPATTERN;


/**
 * Manages the STORAGE of definition of authenticators, not the authentication function
 * @author jhierrot
 */
public abstract class ObjPD
{
/**
 *
 */
private DriverGeneric Drv=null;
/**
 *
 */
static public final String fPDDATE="PDDate";
/**
 *
 */
static public final String fPDAUTOR="PDAutor";
/**
 *
 */
private Date   PDDate;
/**
 *
 */
private String PDAutor;

/**
 *
 */
static private Record CommonStruct=null;

/**
 *
 */
static public final String XML_ListAttr="ListAttr";
/**
 *
 */
static public final String XML_OPDObject="OPDObject";
/**
 *
 */
static public final String XML_OPDList="OPDList";
/**
 *
 */
static public final String XML_GroupMembers="GroupMembers";
/**
 *
 */
static public final String XML_UserMembers="UserMembers";
/**
 *
 */
static public final String XML_Group="Group";
/**
 *
 */
static public final String XML_User="User";
/**
 *
 */
static public final String XML_Metadata="Metadata";
/**
 *
 */
static public final String XML_Field="Field";
/**
 *
 */
static public final String XML_Attr="Attr";

/**
 *
 */
static public final String AllowedChars="abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ_0123456789";
// Ctes for evaluating syntax of tasks of updating

/**
 * SYMBOL for delimiting constants
 */
static public final char SYN_SEP='#';
/**
 * SYMBOL for adding values
 */
static public final char SYN_ADD='+';
/**
 * SYMBOL for substract values
 */
static public final char SYN_DEL='-';
/**
 * SYMBOL for Reference to Parent metadata
 */
public static final char SYN_PARENT='@';
/**
 * SYMBOL for Reference to Thesaur Term Name
 */
public static final char SYN_THES='{';
//-------------------------------------------------------------------------
/**
 *
 */
public ObjPD()
{
}
//-------------------------------------------------------------------------
/**
 * Default constructor of an OpenProdoc object, assigning driver for database access
 * @param pDrv OpenProdoc Driver/session to assign
 */
public ObjPD(DriverGeneric pDrv)
{
setDrv(pDrv);
}
//-------------------------------------------------------------------------
/**
 * Generic method for inserting an element
 * @throws PDException in any error
 */
public void insert() throws PDException
{
boolean InTransLocal;
if (PDLog.isDebug())
    PDLog.Debug("ObjPD.insert>:"+getTabName());
VerifyAllowedIns();
InTransLocal=!getDrv().isInTransaction();
if (InTransLocal)
    getDrv().IniciarTrans();
try {
AddLogFields();
getRecord().Verify();
getDrv().InsertRecord(getTabName(), getRecord());
InsertMulti();
getObjCache().put(getKey(), getRecord());
} catch (PDException Ex)
    {
    getDrv().AnularTrans();
    throw Ex;
    }
if (InTransLocal)
    getDrv().CerrarTrans();
if (PDLog.isDebug())
    PDLog.Debug("ObjPD.insert <");
}
//-------------------------------------------------------------------------
/**
 * Generic method for deleting an element
 * @throws PDException in any error
 */
public void delete()  throws PDException
{
boolean InTransLocal;
if (PDLog.isDebug())
    PDLog.Debug("ObjPD.delete>:"+getTabName());
VerifyAllowedDel();
InTransLocal=!getDrv().isInTransaction();
if (InTransLocal)
    getDrv().IniciarTrans();
try {
DeleteMulti();
getDrv().DeleteRecord(getTabName(), getConditions());
getObjCache().remove(getKey());
} catch (PDException Ex)
    {
    getDrv().AnularTrans();
    throw Ex;
    }
if (InTransLocal)
    getDrv().CerrarTrans();
if (PDLog.isDebug())
    PDLog.Debug("ObjPD.delete <");
}
//------------------------------------------------------------------------
/**
 * Generic method for updating an element
 * @throws PDException in any error
 */
public void update()  throws PDException
{
boolean InTransLocal;
if (PDLog.isDebug())
    PDLog.Debug("ObjPD.update>:"+getTabName());
VerifyAllowedUpd();
InTransLocal=!getDrv().isInTransaction();
if (InTransLocal)
    getDrv().IniciarTrans();
try {
AddLogFields();
getDrv().UpdateRecord(getTabName(), getRecord(), getConditions());
UpdateMulti();
getObjCache().put(getKey(), getRecord());
} catch (PDException Ex)
    {
    getDrv().AnularTrans();
    throw Ex;
    }
if (InTransLocal)
    getDrv().CerrarTrans();
if (PDLog.isDebug())
    PDLog.Debug("ObjPD.update <");
}
//-------------------------------------------------------------------------
/**
 * Generic "abstract" method for obtaining the name of the table
 * @return the name of the DDB table that stores the current OPD object
 */
public String getTabName()
{
return(null);
}
//-------------------------------------------------------------------------
/**
 * Generic "abstract" method for returning a Record of loaded OPD Object
 * @return a record with attributes of the loaded OPD object
 * @throws PDException in any error
 */
public Record getRecord() throws PDException
{
return(null);
}
//-------------------------------------------------------------------------
/**
 * Returns the Structure/definition of current OPD Class (PDUsers, PDGroups, PDAcl,..) as a record 
 * @return a Record containing the definition/structure of current OPD class
 * @throws PDException in any error
 */
Record getRecordStruct() throws PDException
{
return(null);
}
//-------------------------------------------------------------------------
/**
 *
 * @return
 * @throws PDException
 */
abstract protected Conditions getConditions() throws PDException;
//-------------------------------------------------------------------------
/**
 *
 * @param Name
 * @return
 * @throws PDException
 */
abstract protected Conditions getConditionsLike(String Name) throws PDException;
/**
 * Generic "abstract" method for assigning a Record Values to an OPD Object
 * @param Rec Record to be assigned
 * @throws PDException in any error
 */
public void assignValues(Record Rec) throws PDException
{

}
//-------------------------------------------------------------------------
/**
 * Creates the Database structure of OpenProdoc
 * @throws PDException in any error
 */
public void Install() throws PDException
{
getDrv().CreateTable(getTabName(), getRecordStruct());
}
//-------------------------------------------------------------------------
/**
 *
 * @throws PDException
 */
protected void InstallMulti()  throws PDException
{
}
//-------------------------------------------------------------------------
/**
 * Drops DDBB tables of current class
 * @throws PDException in any error
 */
public void unInstall() throws PDException
{
getDrv().DropTable(getTabName());
}
//-------------------------------------------------------------------------

/**
 *
 * @throws PDException
 */
protected void unInstallMulti() throws PDException
{
}
//-------------------------------------------------------------------------
/**
 *
 * @throws PDException
 */
protected void InsertMulti() throws PDException
{
}
//-------------------------------------------------------------------------
/**
 *
 * @throws PDException
 */
protected void UpdateMulti() throws PDException
{
}
//-------------------------------------------------------------------------
/**
 *
 * @throws PDException
 */
protected void DeleteMulti() throws PDException
{
}
//-------------------------------------------------------------------------
/**
 * Load the object of the actual type based on the Iden
 * @param Ident Identifier of the objetct to be loaded
 * @return A record with the attributes of the object
 * @throws PDException In any error
 */
public Record Load(String Ident)  throws PDException
{
if (PDLog.isDebug())
    PDLog.Debug("ObjPD.Load>:"+getTabName()+"-"+Ident);
AsignKey(Ident);
Record r=(Record)getObjCache().get(Ident);
if (r==null)
    {
    Query LoadAct=new Query(getTabName(), getRecordStruct(),getConditions());
    Cursor Cur=null;
    try {
    Cur=getDrv().OpenCursor(LoadAct);
    r=getDrv().NextRec(Cur);
    getDrv().CloseCursor(Cur);
    } catch (Exception Ex)
        {
        if (Cur!=null)
           getDrv().CloseCursor(Cur); 
        PDException.GenPDException("Error_loading_in_cache", Ex.getLocalizedMessage());
        }
    getObjCache().put(Ident, r);
    }
if (r!=null) // can be null the result loaded (and assigned/cleaned to cache
    assignValues(r);
if (PDLog.isDebug())
    PDLog.Debug("ObjPD.Load <");
return(r);
}
//-------------------------------------------------------------------------
/**
 *
 * @param Ident
 * @throws PDException  
 */
abstract protected void AsignKey(String Ident) throws PDException;
//-------------------------------------------------------------------------
    /**
     *
     * @return
     */
    abstract protected String getKey();
//-------------------------------------------------------------------------
/**
 * @return the OPD "Driver" Session of the current object
 * @throws PDException In any error
*/
public DriverGeneric getDrv()  throws PDException
{
if (Drv==null)
    PDException.GenPDException("Driver_not_assigned", null);
return Drv;
}
//-------------------------------------------------------------------------
/**
* @param Drv the Drv to set
*/
public void setDrv(DriverGeneric Drv)
{
this.Drv = Drv;
}
//-------------------------------------------------------------------------
/**
 * Deletes the values assigned to the current object
 * @throws PDException in any error
 */
public void Clear() throws PDException
{
assignValues(getRecordStruct());
}
/**
 *
 * @throws PDException
 */
abstract protected void VerifyAllowedIns() throws PDException;
/**
 *
 * @throws PDException
 */
abstract protected void VerifyAllowedDel() throws PDException;
/**
 *
 * @throws PDException
 */
abstract protected void VerifyAllowedUpd() throws PDException;
//-------------------------------------------------------------------------
/**
* @return the PDDate
*/
public Date getPDDate()
{
return PDDate;
}
//-------------------------------------------------------------------------
/**
* @param PDDate the PDDate to set
*/
protected void setPDDate(Date PDDate)
{
this.PDDate = PDDate;
}
//-------------------------------------------------------------------------

/**
 * Returns the fixed structure
 * @return
 * @throws PDException
 */
static protected Record getRecordStructCommon() throws PDException
{
if (CommonStruct==null)
    {
    CommonStruct=CreateRecordStructCommon();
    }
return(CommonStruct.Copy());
}
//-------------------------------------------------------------------------
/**
 *
 * @return
 * @throws PDException
 */
static private synchronized Record CreateRecordStructCommon() throws PDException
{
if (CommonStruct==null)
    {
    Record R=new Record();
    R.addAttr( new Attribute(fPDAUTOR, "Author", "Name_of_Author", Attribute.tSTRING, true, null, 32, false, false, false ));
    R.addAttr( new Attribute(fPDDATE, "Date", "Date_of_update_in_repository", Attribute.tTIMESTAMP, true, null, 0, false, false, false));
    return(R);
    }
else
    return(CommonStruct);
}
//-------------------------------------------------------------------------
/**
* @return the PDAutor
*/
public String getPDAutor()
{
return PDAutor;
}
//-------------------------------------------------------------------------
/**
* @param PDAutor the PDAutor to set
*/
protected void setPDAutor(String PDAutor)
{
this.PDAutor = PDAutor;
}
//-------------------------------------------------------------------------
/**
 *
 * @throws PDException
 */
protected void AddLogFields() throws PDException
{
setPDDate(new Date());
setPDAutor(getDrv().getUser().getName());
}
//-------------------------------------------------------------------------
/**
 *
 * @param Rec
 * @throws PDException
 */
protected void assignCommonValues(Record Rec)  throws PDException
{
setPDDate((Date) Rec.getAttr(fPDDATE).getValue());
setPDAutor((String)Rec.getAttr(fPDAUTOR).getValue());
}
//-------------------------------------------------------------------------
/**
 *
 * @param Rec
 * @throws PDException
 */
protected void getCommonValues(Record Rec)  throws PDException
{
Rec.getAttr(fPDDATE).setValue(getPDDate());
Rec.getAttr(fPDAUTOR).setValue(getPDAutor());
}
//-------------------------------------------------------------------------
/**
 * Do a Query By Example (bases in values assigned to Atributes) anc returns a Cursor
 * @return a created Cursor
 * @throws PDException in any error
 */
public Cursor SearchQBE() throws PDException
{
if (PDLog.isDebug())
    PDLog.Debug("ObjPD.SearchQBE>:"+getTabName());
Conditions CondQBE=new Conditions();
Query QBE=new Query(getTabName(), getRecordStruct(),CondQBE);
if (PDLog.isDebug())
    PDLog.Debug("ObjPD.SearchQBE <");
return(getDrv().OpenCursor(QBE));
}
//-------------------------------------------------------------------------
/**
 * Search object of the current OPD classby its main identifier (Name, Code,..) using Like DDBB expression
 * @param Name Name to Search for
 * @return a created cursor
 * @throws PDException In any error
 */
public Cursor SearchLike(String Name) throws PDException
{
Query qLike=new Query(getTabName(), getRecordStruct(), getConditionsLike(Name), getDefaultOrder());
return(getDrv().OpenCursor(qLike));
}
//-------------------------------------------------------------------------
/**
 * Search object of the current OPD classby its descriptionusing Like DDBB expression
 * @param Name Description of the Object
 * @return a created cursor
 * @throws PDException In any error
 */
public Cursor SearchLikeDesc(String Name) throws PDException
{
Query qLike=new Query(getTabName(), getRecordStruct(), getConditionsLike(Name), getDescOrder());
return(getDrv().OpenCursor(qLike));
}
//-------------------------------------------------------------------------
/**
 *
 * @param Name
 * @return
 */
protected String VerifyWildCards(String Name)
{
if (!Name.contains("*"))
    Name+="*";
return(Name.replace('*','%')); // OJO: Revisar para Driver NO BBDD
}
/**
 *
 * @return
 */
abstract protected String getDefaultOrder();
//-------------------------------------------------------------------------
/**
 * Returns ALL the elements of the current OPD Class
 * @return A created Cursor
 * @throws PDException In any error
 */
public Cursor getAll() throws PDException
{
Query qLike=new Query(getTabName(), getRecordStruct(), null, getDefaultOrder());
return(getDrv().OpenCursor(qLike));
}
//-------------------------------------------------------------------------
/**
 * Create if necesary and Assign the Cache for the objects of this type of object
 * @return the cache object for the type
 */
abstract protected ObjectsCache getObjCache();
//-------------------------------------------------------------------------
/**
 * Builds an XML of the object to be printed or exported
 * @return the XML created
 * @throws PDException In any error 
 */
public String toXML() throws PDException
{
StringBuilder XML=new StringBuilder("<"+XML_OPDObject+" type=\""+getTabName()+"\">\n");
XML.append("<"+XML_ListAttr+">\n");
XML.append(getRecord().toXML());
XML.append(toXML2());
XML.append("</"+XML_OPDObject+">\n");
return XML.toString();
}
//-------------------------------------------------------------------------
/**
 * Returns the Start String of OpenProdoc XML export format
 * @return The Start String  of OpenProdoc XML export format
 */
public String StartXML()
{
return("<"+XML_OPDList+">\n");    
}
//-------------------------------------------------------------------------
/**
 * Returns the END String  of OpenProdoc XML export format
 * @return The END String  of OpenProdoc XML export format
 */
public String EndXML()
{
return("</"+XML_OPDList+">\n");    
}
//-------------------------------------------------------------------------
/**
 * Add aditional information, oriented a "extended" object with childrn nodes
 * @return The aditional XML
 * @throws PDException
 */
protected String toXML2() throws PDException
{
return("</"+XML_ListAttr+">\n");    
}
//-------------------------------------------------------------------------
/**
 * Process the object definition inserting a new object
 * @param OPDObject XML node containing theobject data
 * @throws PDException if object name/index duplicated or in any error
 */
public void ProcesXMLNode(Node OPDObject) throws PDException
{
NodeList childNodes = OPDObject.getChildNodes();
for (int i = 0; i < childNodes.getLength(); i++)
    {
    Node item = childNodes.item(i);
    if (item.getNodeName().equalsIgnoreCase(XML_ListAttr)) 
        {
        Record r=Record.FillFromXML(item, getRecord());
        assignValues(r);
        }
    }
insert();
}    
//-------------------------------------------------------------------------
/**
 * Verifis if the name is correct to be used as object or attribute name
 * @param Name name to be verified
 * @return the received name
 * @throws PDExceptionFunc in the name is not correct
 */
public static String CheckName(String Name) throws PDExceptionFunc
{
// pendiente de resolver compatibilidad con clear()
if (Name==null)    
    return(Name);
Name=Name.trim();
if (Name.length()==0)   
    PDExceptionFunc.GenPDException("Empty_Name_not_allowed",Name);
if (Name.length()>32)   
    PDExceptionFunc.GenPDException("Name_longer_than_allowed",Name);
for (int i=0; i<Name.length(); i++)
    {
    if (AllowedChars.indexOf(Name.charAt(i))==-1)
       PDExceptionFunc.GenPDException("Character_not_included_in_the_allowed_set",AllowedChars);
    }
return(Name);
}
//---------------------------------------------------------------------
/**
 * Updates fields. Syntax:
 * Field1=Field2;
 * Field1=Field1+Field2;
 * @param param Expresión to use
 * @param r Record of the document or folder to update
 * @param rParent Record of the Parent Folder
 * @param ListThes List of names/values of the Thesaur elements
 * @return Updated record
 * @throws PDException in any error
 */
protected Record Update(String param, Record r, Record rParent, HashMap<String, String> ListThes) throws PDException
{
if (param==null || param.length()==0)    
    return(r);
String DestAttr=param.substring(0, param.indexOf('='));
if (DestAttr.equalsIgnoreCase(PDDocs.fDOCTYPE) || DestAttr.equalsIgnoreCase(PDDocs.fPDID) 
    || DestAttr.equalsIgnoreCase(PDDocs.fREPOSIT) || DestAttr.equalsIgnoreCase(PDDocs.fVERSION)
    || DestAttr.equalsIgnoreCase(PDDocs.fLOCKEDBY) || DestAttr.equalsIgnoreCase(PDDocs.fPDAUTOR)
    || DestAttr.equalsIgnoreCase(PDDocs.fPDDATE) || DestAttr.equalsIgnoreCase(PDDocs.fSTATUS))
    PDExceptionFunc.GenPDException("Attribute_not_allowed_to_change", DestAttr);
Attribute Att=r.getAttr(DestAttr);
String NewExp=param.substring(param.indexOf('=')+1);
ArrayList<String> ListElem=new ArrayList<String>(NewExp.length()/4);
String Current="";
boolean Constant=false;
for (int i = 0; i < NewExp.length(); i++)
    {
    char c=NewExp.charAt(i);
    if (c!=SYN_SEP && c!=SYN_ADD && c!=SYN_DEL)  
        Current+=c;
    else if (c==SYN_ADD || c==SYN_DEL)
        {
        if (Current.length()!=0)    
            {
            ListElem.add(Current);
            Current="";
            }
        ListElem.add(""+c);
        }
    else if (c==SYN_SEP)
        {
        Current+=c;
        if (Constant)
            {
            ListElem.add(Current);
            Current="";
            }
        Constant=!Constant;
        }
    }
if (Current.length()!=0)
    {
    ListElem.add(Current);
    Current="";
    }
String TotalVal="";
String NewVal;
Attribute Attr1;
for (int i = 0; i < ListElem.size(); i++)
    {
    if (i % 2 == 0) // Field 
        {
        String Elem = ListElem.get(i);
        if (Elem.charAt(0)==SYN_SEP)
            NewVal=Elem.substring(1, Elem.length()-1);
        else if (Elem.charAt(0)==SYN_PARENT)
            {
            Attr1=rParent.getAttr(Elem.substring(1));
            NewVal=Attr1.Export();
            }       
         else if (Elem.charAt(0)==SYN_THES)
            {
            NewVal=ListThes.get(Elem.substring(1));
            }       
        else 
            {
            Attr1=r.getAttr(Elem);
            NewVal=Attr1.Export();
            }       
        if (Current.equals(""))
            {
            TotalVal=NewVal;
            }
        else if (Current.charAt(0)==SYN_ADD)
            {
            if (Att.getType()==Attribute.tDATE)
               TotalVal=AddDate(TotalVal, NewVal);
            else if (Att.getType()==Attribute.tSTRING)            
               TotalVal+=NewVal;
            }
        }
    else
        {
        Current=ListElem.get(i);
        }
    }
r.getAttr(DestAttr).Import(TotalVal);
return(r);
}
//---------------------------------------------------------------------
/**
 * Adds dates in the export/import format. Dates can be "partial" that is date with some 00 values.
 * @param TotalVal Date/partial date to be added YYYY-MM-DD
 * @param NewVal Date/partial date to be added   YYYY-MM-DD
 * @return Date/partial date to be added YYYY-MM-DD
 */
private String AddDate(String TotalVal, String NewVal)
{
int Year1=Integer.parseInt(TotalVal.substring(0,4));
int Month1=Integer.parseInt(TotalVal.substring(5,7));
int Day1=Integer.parseInt((TotalVal+" ").substring(8,10));
Year1+=Integer.parseInt(NewVal.substring(0,4));
Month1+=Integer.parseInt(NewVal.substring(5,7));
Day1+=Integer.parseInt((NewVal+" ").substring(8,10));
return(String.format("%04d", Year1)+"-"+String.format("%02d", Month1)+"-"+String.format("%02d", Day1));
}
//---------------------------------------------------------------------
/**
 *
 * @return
 */
protected String getDescOrder()
{
return(PDObjDefs.fDESCRIPTION);
}
//---------------------------------------------------------------------
/**
 * Run a query and return the result as aVector or records
 * @param SQL Query to run
 * @return Vector of results as Records
 * @throws PDException in any error
 */
public Vector<Record> SearchSelectV(String SQL) throws PDException
{
Vector<Record> ListRes=new Vector<Record>(); 
Cursor CurSelect = SearchSelect(SQL);
try {
Record NextRec=getDrv().NextRec(CurSelect);
while (NextRec!=null)
    {
    ListRes.add(NextRec);
    NextRec=getDrv().NextRec(CurSelect);
    }
} 
finally 
    {
    getDrv().CloseCursor(CurSelect);
    }
return(ListRes);
}

/**
 * Search in ANY object using SQL Syntax subset, similar to CMIS SQL
 * @param SQL complete query
 * @return and opened @Cursor
 * @throws PDException In any Error
 */
public Cursor SearchSelect(String SQL) throws PDException
{
if (PDLog.isDebug())
    PDLog.Debug("ObjPD.SearchSelect>:"+SQL);
Query QBE=null;
try {
Select ParsedSQL = (Select) CCJSqlParserUtil.parse(SQL);
//-- Calculate Table Names ------------
TablesNamesFinder tablesNamesFinder = new TablesNamesFinder();
List<String> TableListSQL = tablesNamesFinder.getTableList(ParsedSQL);
Vector <String> OPDTabs=CalculateTabs(TableListSQL);
//-- Calculate Fields -------------
List<SelectItem> selectItems = ((PlainSelect)ParsedSQL.getSelectBody()).getSelectItems();
Vector<String> Fields=new Vector<String>();
if (!( selectItems.get(0) instanceof AllColumns))
    for (int i = 0; i < selectItems.size(); i++)
        Fields.add(((SelectExpressionItem)selectItems.get(i)).getExpression().toString());      
Record Rec=CalculateRec(Fields, OPDTabs);
//-- Calculate Conds in Select ------------
Expression When = ((PlainSelect)ParsedSQL.getSelectBody()).getWhere();
Conditions CondSel=EvalExpr(When);

//-- Check Additional-Security Conditions ----
Conditions FinalConds;
Conditions AddedConds=NeededMoreConds(TableListSQL, OPDTabs);
if (AddedConds==null)
    FinalConds=CondSel;
else
    {
    FinalConds=new Conditions();
    FinalConds.addCondition(AddedConds);
    FinalConds.addCondition(CondSel);
    }
//-- Calculate Order ------------
Vector <String> Order=new Vector<String>();
Vector <Boolean> OrderAsc=new Vector<Boolean>();
List<OrderByElement> orderByElements = ((PlainSelect)ParsedSQL.getSelectBody()).getOrderByElements(); 
if (orderByElements!=null)
    for (int i = 0; i < orderByElements.size(); i++)
        {
        Order.add(orderByElements.get(i).getExpression().toString());
        OrderAsc.add(orderByElements.get(i).isAsc());
        }
//-- Create Query --------------
QBE=new Query(OPDTabs, Rec, FinalConds, Order, OrderAsc);
if (PDLog.isDebug())
    PDLog.Debug("ObjPD.SearchSelect <");
} catch (Exception Ex)
    {
    Ex.printStackTrace();
    PDException.GenPDException("Processing_SQL", Ex.getLocalizedMessage());
    }
return(getDrv().OpenCursor(QBE));
}
//-------------------------------------------------------------------------
/**
 *
 * @param tableList
 * @return
 * @throws PDException in any error
 */
protected Vector<String> CalculateTabs(List<String> tableList) throws PDException
{
Vector <String> Tabs=new Vector<String>();
Tabs.add(getTabName());
return(Tabs);
}
//-------------------------------------------------------------------------

    /**
     *
     * @param Fields
     * @param Tabs
     * @return
     * @throws PDException in any error
     */
protected Record CalculateRec(Vector<String> Fields, Vector <String> Tabs) throws PDException
{
if (Fields.isEmpty())    
    return getRecordStruct();
Record R=getRecordStruct();
Record R2=new Record();
Attribute attr;
for (int i = 0; i < Fields.size(); i++)
    {
    attr = R.getAttr(Fields.elementAt(i));
    if (attr!=null)
        R2.addAttr(attr);
    }
if (R2.NumAttr()==0)
    PDException.GenPDException("Empty_or_Erroneus_list_of_Fields", null);
return(R2);
}
//-------------------------------------------------------------------------
/**
 *
     * @param tableListSQL
     * @param OPDTabs
 * @return
     * @throws prodoc.PDException
 */
protected Conditions NeededMoreConds(List<String> tableListSQL, Vector <String> OPDTabs) throws PDException
{
return(null);
}
//-------------------------------------------------------------------------
static private HashMap<String, Integer>CompConv=new HashMap<String, Integer>(); 

static private final int EXPR_BASIC=0;
static private final int EXPR_AND=1;
static private final int EXPR_OR=2;
static private final int EXPR_PAR=3;
static private final int EXPR_FUNCT=4;
static private final int EXPR_IN=5;
static private final int EXPR_NOT=6;

//---------------------------------------------------------------------------
static private synchronized HashMap<String, Integer>getCompConv()
{
if (CompConv.isEmpty())   
    {
    CompConv.put("=", Condition.cEQUAL);
    CompConv.put(">=", Condition.cGET );
    CompConv.put("<=", Condition.cLET);
    CompConv.put(">", Condition.cGT);
    CompConv.put("<", Condition.cLT);
    CompConv.put("<>", Condition.cNE);
    }
return(CompConv);
}
//---------------------------------------------------------------------------
private boolean isField(String Text)
{
char c=Text.toLowerCase().charAt(0);
return(c>='a' && c<='z');
}
final SimpleDateFormat formatterTS = new SimpleDateFormat("yyyy-MM-dd'T'HH:mm:ss");
/**
 * Default formater, used to store in DDBB, export, etc
 */
final SimpleDateFormat formatterDate = new SimpleDateFormat("yyyy-MM-dd");

//---------------------------------------------------------------------------
private Object CalcVal(String Text)
{
//System.out.println("CalcVal=("+Text+")");        
if (Text==null || Text.length()==0)    
    return(Text);
if (Text.charAt(0)=='\'')
    {
    Text=Text.substring(1, Text.length()-1);    
    try {
        return(formatterTS.parse(Text));
    } catch (ParseException ex)
        { // No Timestamp
        }
    try {
        return(formatterDate.parse(Text));
    } catch (ParseException ex)
        { // No Date
        }
    return Text;
    }
if (Text.contains(".") || Text.contains("."))
    return(String2BD(Text));
else
    return(Integer.valueOf(Text));  
}
//---------------------------------------------------------------------------
private int CalcTypeVal(String Text)
{
//System.out.println("CalcTypeVal=("+Text+")");        
if (Text==null || Text.length()==0)    
    return(Attribute.tSTRING);
if (Text.charAt(0)=='\'')
    {
    Text=Text.substring(1, Text.length()-1);    
    try {
        formatterTS.parse(Text);
        return(Attribute.tTIMESTAMP);
    } catch (ParseException ex)
        { // No Timestamp
        }
    try {
        formatterDate.parse(Text);
        return(Attribute.tDATE);
    } catch (ParseException ex)
        { // No Date
        }
    return (Attribute.tSTRING);
    }
if (Text.contains(".") || Text.contains("."))
    return((Attribute.tFLOAT));
else
    return((Attribute.tINTEGER));  
}
//---------------------------------------------------------------------------
/**
 * Evaluates a String stored in Database in OPD format and returns the BigDecimal that represents
 * @param SBD Sttring representing a BigDecimal
 * @return BigDecimal
 */
public BigDecimal String2BD(String SBD)
{
DecimalFormat DF=new DecimalFormat(DECIMALPATTERN);
return(new BigDecimal(DF.format(new BigDecimal(SBD.replace(',','.').replace("_", ""))).replace(',','.').replace("_", "")));
}
//---------------------------------------------------------------------------
private int EvalExprType(Expression where)
{
if (where instanceof AndExpression)
    return (EXPR_AND);
else if (where instanceof OrExpression)
    return (EXPR_OR);
else if (where instanceof BinaryExpression)
    return(EXPR_BASIC);
else if (where instanceof Function)
    return(EXPR_FUNCT);
else if (where instanceof Parenthesis)
    return (EXPR_PAR);
else if (where instanceof InExpression) 
    return (EXPR_IN);
else if (where instanceof NotExpression) 
    return (EXPR_NOT);
return(-1);
}
//------------------------------------------------------------------------------
private Conditions EvalExpr(Expression ParentExpr ) throws PDException 
{
Conditions New = new Conditions();    
int ExprType= EvalExprType(ParentExpr);
//System.out.println("ParentExpr=["+ParentExpr+"]  Type="+ExprType);    
switch (ExprType)
    {
    case EXPR_BASIC:
        ComparisonOperator CO = (ComparisonOperator) ParentExpr;
        String Left=CO.getLeftExpression().toString();
        String Comp=CO.getStringExpression();
        String Right=CO.getRightExpression().toString();
        if (isField(Left) && isField(Right))
            New.addCondition(new Condition(Left,  Right));
        else  
            {
            String FieldName;
            Object Value;
            int TypeVal;
            if (isField(Left))
                {
                FieldName=Left;
                Value=CalcVal(Right);
                TypeVal=CalcTypeVal(Right);
                }
            else
                {
                FieldName=Right;
                Value=CalcVal(Left);
                TypeVal=CalcTypeVal(Left);
                }
//            System.out.println("Value="+Value+"  class="+Value.getClass().getName());
            New.addCondition(new Condition(FieldName,  getCompConv().get(Comp), Value, TypeVal));
            }
        break;    
    case EXPR_NOT:
        Conditions Cs=EvalExpr(((NotExpression) ParentExpr).getExpression());
        Cs.setInvert(true);
        New.addCondition(Cs);
        break;
    case EXPR_AND:
        New.addCondition(EvalExpr(((AndExpression) ParentExpr).getLeftExpression() ));
        New.addCondition(EvalExpr(((AndExpression) ParentExpr).getRightExpression() ));
        break;
    case EXPR_OR:
        New.addCondition(EvalExpr(((OrExpression) ParentExpr).getLeftExpression() ));
        New.addCondition(EvalExpr(((OrExpression) ParentExpr).getRightExpression() ));
        New.setOperatorAnd(false);
        break;
    case EXPR_PAR:
        New.addCondition(EvalExpr(((Parenthesis) ParentExpr).getExpression() ));
        break;
    case EXPR_IN:
        String FieldNameIn=((InExpression)ParentExpr).getLeftExpression().toString();
        HashSet<String> ListTerms = new HashSet<String>();
        List<Expression> LT =((ExpressionList)((InExpression)ParentExpr).getLeftItemsList()).getExpressions();
        for (Iterator<Expression> iterator = LT.iterator(); iterator.hasNext();)
            {
            StringValue NextTerm = (StringValue)iterator.next();
            ListTerms.add(NextTerm.getValue());
            }
        New.addCondition(new Condition(FieldNameIn,ListTerms));
        break;
    case EXPR_FUNCT:
        String Arg=((Function)ParentExpr).getParameters().getExpressions().get(0).toString();
        switch (((Function)ParentExpr).getName())
            {
            case Condition.CONTAINS:
                New.addCondition(Condition.genContainsCond(PDDocs.getTableName(),Arg, getDrv())); 
                break;
            case Condition.INTREE:
                New.addCondition(Condition.genInTreeCond( Arg, getDrv())); 
                break;
            case Condition.INFOLDER:
                New.addCondition(Condition.genInFolder(Arg, getDrv()));
                break;
                
            }
        break;    
        
    }
return(New);
}
//------------------------------------------------------------------------------
}