001/**
002 *
003 * Copyright (c) 2014, the Railo Company Ltd. All rights reserved.
004 *
005 * This library is free software; you can redistribute it and/or
006 * modify it under the terms of the GNU Lesser General Public
007 * License as published by the Free Software Foundation; either 
008 * version 2.1 of the License, or (at your option) any later version.
009 * 
010 * This library is distributed in the hope that it will be useful,
011 * but WITHOUT ANY WARRANTY; without even the implied warranty of
012 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
013 * Lesser General Public License for more details.
014 * 
015 * You should have received a copy of the GNU Lesser General Public 
016 * License along with this library.  If not, see <http://www.gnu.org/licenses/>.
017 * 
018 **/
019package lucee.transformer.library.function;
020
021import java.io.IOException;
022import java.io.Reader;
023import java.net.URISyntaxException;
024import java.nio.charset.Charset;
025import java.util.ArrayList;
026import java.util.HashMap;
027import java.util.Iterator;
028import java.util.Map;
029import java.util.Map.Entry;
030import java.util.Set;
031
032import lucee.commons.io.IOUtil;
033import lucee.commons.io.res.Resource;
034import lucee.commons.io.res.filter.ExtensionResourceFilter;
035import lucee.commons.io.res.util.ResourceUtil;
036import lucee.runtime.op.Caster;
037import lucee.runtime.text.xml.XMLUtil;
038import lucee.runtime.type.util.ArrayUtil;
039import lucee.transformer.library.tag.TagLibFactory;
040
041import org.xml.sax.Attributes;
042import org.xml.sax.InputSource;
043import org.xml.sax.SAXException;
044import org.xml.sax.XMLReader;
045import org.xml.sax.helpers.DefaultHandler;
046
047
048
049/**
050 *
051 * Die FunctionLibFactory ist der Produzent fuer eine oder mehrere FunctionLib, 
052 * d.H. ueber statische Methoden (get, getDir) koennen FunctionLibs geladen werden. 
053 * Die FunctionLibFactory erbt sich vom DefaultHandler. 
054 */
055public final class FunctionLibFactory extends DefaultHandler {
056        private XMLReader xmlReader;
057        //private File file;
058        private boolean insideFunction=false,insideAttribute=false,insideReturn=false;
059        private String inside;
060        private StringBuffer content=new StringBuffer();
061        /**
062         * Definiert den Default SAX Parser
063         */
064        public final static String DEFAULT_SAX_PARSER="org.apache.xerces.parsers.SAXParser";
065        
066        
067        private static Map<String,FunctionLib> hashLib=new HashMap<String,FunctionLib>();
068        private static FunctionLib systemFLD;
069        private FunctionLib lib=new FunctionLib();
070        private FunctionLibFunction function;
071
072        private FunctionLibFunctionArg arg;
073        
074        private final static String FLD_1_0=    "/resource/fld/web-cfmfunctionlibrary_1_0";
075        
076
077        
078        /**
079         * Privater Konstruktor, der als Eingabe die FLD als InputStream erhaelt.
080         * @param saxParser String Klassenpfad zum Sax Parser.
081         * @param is InputStream auf die TLD.
082         * @throws FunctionLibException
083         
084        private FunctionLibFactory(String saxParser,InputSource is) throws FunctionLibException {
085                super();
086                init(saxParser,is);
087        }*/
088        
089        /**
090         * Privater Konstruktor, der als Eingabe die FLD als File Objekt erhaelt.
091         * @param saxParser String Klassenpfad zum Sax Parser.
092         * @param file File Objekt auf die TLD.
093         * @throws FunctionLibException
094         */
095        private FunctionLibFactory(String saxParser,Resource file) throws FunctionLibException {
096                super();
097                Reader r=null;
098                try {
099                        init(saxParser,new InputSource(r=IOUtil.getReader(file.getInputStream(), (Charset)null)));
100                } catch (IOException e) {
101                        throw new FunctionLibException("File not found: "+e.getMessage());
102                }
103                finally {
104                        IOUtil.closeEL(r);
105                }
106        }
107        
108        /**
109         * Privater Konstruktor nur mit Sax Parser Definition, liest Default FLD vom System ein.
110         * @param saxParser String Klassenpfad zum Sax Parser.
111         * @throws FunctionLibException
112         */
113        private FunctionLibFactory(String saxParser) throws FunctionLibException {
114                super();
115                InputSource is=new InputSource(this.getClass().getResourceAsStream(FLD_1_0) );
116                init(saxParser,is);             
117        }
118        
119        /**
120         * Generelle Initialisierungsmetode der Konstruktoren.
121         * @param saxParser String Klassenpfad zum Sax Parser.
122         * @param is InputStream auf die TLD.
123         * @throws FunctionLibException
124         */
125        private void init(String saxParser,InputSource is) throws FunctionLibException  {
126                
127                
128                
129                try {
130
131                        xmlReader=XMLUtil.createXMLReader(saxParser);
132                        xmlReader.setContentHandler(this);
133                        xmlReader.setErrorHandler(this);
134                        xmlReader.setEntityResolver(new FunctionLibEntityResolver());
135                        xmlReader.parse(is);
136                } catch (IOException e) {
137                        
138                        throw new FunctionLibException("IO Exception: "+e.getMessage());
139                } catch (SAXException e) {
140                        throw new FunctionLibException("SaxException: "+e.getMessage());
141                }
142                
143    }
144            
145        /**
146         * Geerbte Methode von org.xml.sax.ContentHandler, 
147         * wird bei durchparsen des XML, beim Auftreten eines Start-Tag aufgerufen.
148         *  
149         * @see org.xml.sax.ContentHandler#startElement(String, String, String, Attributes)
150         */
151    public void startElement (String uri, String name,
152                              String qName, Attributes atts)    {
153    // Start Function
154        inside=qName;
155        if(qName.equals("function")) startFunction();
156        else if(qName.equals("argument")) startArg();
157        else if(qName.equals("return")) startReturn();
158    }
159    
160        /**
161         * Geerbte Methode von org.xml.sax.ContentHandler, 
162         * wird bei durchparsen des XML, beim auftreten eines End-Tag aufgerufen.
163         *  
164         * @see org.xml.sax.ContentHandler#endElement(String, String, String)
165         */
166    public void endElement (String uri, String name, String qName)      {
167                setContent(content.toString().trim());
168                content=new StringBuffer();
169        inside="";
170        if(qName.equals("function")) endFunction();
171        else if(qName.equals("argument")) endArg();
172        else if(qName.equals("return")) endReturn();
173                
174    }
175    
176        /**
177         * Wird jedesmal wenn das Tag function beginnt aufgerufen, 
178         * um intern in einen anderen Zustand zu gelangen.
179         */
180    private void startFunction()        {
181        function=new FunctionLibFunction();
182        insideFunction=true;
183    }
184    
185        /**
186         * Wird jedesmal wenn das Tag function endet aufgerufen, 
187         * um intern in einen anderen Zustand zu gelangen.
188         */
189    private void endFunction()  {
190        lib.setFunction(function);
191        insideFunction=false;
192    }
193    
194        /**
195         * Wird jedesmal wenn das Tag argument beginnt aufgerufen, 
196         * um intern in einen anderen Zustand zu gelangen.
197         */
198    private void startArg()     {
199        insideAttribute=true;
200        arg=new FunctionLibFunctionArg();
201    }
202    
203    /**
204         * Wird jedesmal wenn das Tag argument endet aufgerufen, 
205         * um intern in einen anderen Zustand zu gelangen.
206         */
207    private void endArg()       {
208        function.setArg(arg);
209        insideAttribute=false;
210    }
211    
212    /**
213         * Wird jedesmal wenn das Tag return beginnt aufgerufen, 
214         * um intern in einen anderen Zustand zu gelangen.
215         */
216    private void startReturn()  {
217        insideReturn=true;
218    }
219    
220    /**
221         * Wird jedesmal wenn das Tag return endet aufgerufen, 
222         * um intern in einen anderen Zustand zu gelangen.
223         */
224    private void endReturn()    {
225        insideReturn=false;
226    }
227    
228        /**
229         * Geerbte Methode von org.xml.sax.ContentHandler, 
230         * wird bei durchparsen des XML, zum einlesen des Content eines Body Element aufgerufen.
231         * 
232         * @see org.xml.sax.ContentHandler#characters(char[], int, int)
233         */
234        public void characters (char ch[], int start, int length)       {
235                content.append(new String(ch,start,length));
236        }
237        
238        private void setContent(String value)   {
239                if(insideFunction)      {                       
240                        // Attributes Value
241                        if(insideAttribute)     {
242                                if(inside.equals("type")) arg.setType(value);
243                                else if(inside.equals("name")) arg.setName(value);
244                                else if(inside.equals("default")) arg.setDefaultValue(value);
245                                else if(inside.equals("default-value")) arg.setDefaultValue(value); // deprecated
246                                else if(inside.equals("status")) arg.setStatus(TagLibFactory.toStatus(value));
247                                else if(inside.equals("description")) arg.setDescription(value);
248                                else if(inside.equals("alias")) arg.setAlias(value);
249                                
250                                else if(inside.equals("required"))      {
251                                        arg.setRequired(value);
252                                        if(arg.isRequired())
253                                                function.setArgMin(function.getArgMin()+1);
254                                }
255                        }
256                        // Return Values
257                        else if(insideReturn)   {
258                                if(inside.equals("type"))
259                                        function.setReturn(value);
260                        }
261                        // Function Value
262                        else    {
263                                if(inside.equals("name"))
264                                        function.setName(value);
265                                
266                                else if(inside.equals("class"))
267                                        function.setCls(value);
268                                
269                                else if(inside.equals("tte-class"))
270                                        function.setTteClass(value);
271                                if(inside.equals("keywords"))
272                                        function.setKeywords(value);
273                                
274
275                                else if(inside.equals("description"))
276                                        function.setDescription(value);
277
278                                else if(inside.equals("member-name"))
279                                        function.setMemberName(value);
280                                else if(inside.equals("member-position"))
281                                        function.setMemberPosition(Caster.toIntValue(value,1));
282                                else if(inside.equals("member-chaining"))
283                                        function.setMemberChaining(Caster.toBooleanValue(value,false));
284                                
285                                else if(inside.equals("status"))
286                                        function.setStatus(TagLibFactory.toStatus(value));
287                                
288                                else if(inside.equals("argument-type"))
289                                        function.setArgType(value.equalsIgnoreCase("dynamic")?FunctionLibFunction.ARG_DYNAMIC:FunctionLibFunction.ARG_FIX);
290                                
291                                else if(inside.equals("argument-min"))
292                                        function.setArgMin(Integer.parseInt(value));
293                                
294                                else if(inside.equals("argument-max"))
295                                        function.setArgMax(Integer.parseInt(value));
296                        }
297                        
298                }
299                else {
300                        //function lib values
301                        if(inside.equals("flib-version")) lib.setVersion(value);
302                        else if(inside.equals("short-name")) lib.setShortName(value);
303                        else if(inside.equals("uri")) { 
304                                try {
305                                        lib.setUri(value);
306                                } catch (URISyntaxException e) {} 
307                        } 
308                        else if(inside.equals("display-name")) lib.setDisplayName(value);
309                        else if(inside.equals("description")) lib.setDescription(value);                
310                }               
311    }
312
313        /**
314         * Gibt die interne FunctionLib zurueck.
315         * @return Interne Repraesentation der zu erstellenden FunctionLib.
316         */
317        private FunctionLib getLib() {
318                return lib;
319        }
320        
321        /**
322         * Laedt mehrere FunctionLib's die innerhalb eines Verzeichnisses liegen.
323         * @param dir Verzeichnis im dem die FunctionLib's liegen.
324         * @return FunctionLib's als Array
325         * @throws FunctionLibException
326         */
327        public static FunctionLib[] loadFromDirectory(Resource dir) throws FunctionLibException {
328                return loadFromDirectory(dir,DEFAULT_SAX_PARSER);
329        }
330        
331        /**
332         * Laedt mehrere FunctionLib's die innerhalb eines Verzeichnisses liegen.
333         * @param dir Verzeichnis im dem die FunctionLib's liegen.
334         * @param saxParser Definition des Sax Parser mit dem die FunctionLib's eingelesen werden sollen.
335         * @return FunctionLib's als Array
336         * @throws FunctionLibException
337         */
338        public static FunctionLib[] loadFromDirectory(Resource dir,String saxParser) throws FunctionLibException        {
339                if(!dir.isDirectory())return new FunctionLib[0];
340                ArrayList<FunctionLib> arr=new ArrayList<FunctionLib>();
341                
342                Resource[] files=dir.listResources(new ExtensionResourceFilter("fld"));
343                for(int i=0;i<files.length;i++)      {
344                        if(files[i].isFile())
345                                arr.add(FunctionLibFactory.loadFromFile(files[i],saxParser));                           
346                }
347        
348                return arr.toArray(new FunctionLib[arr.size()]);
349        }
350        /**
351         * Laedt eine einzelne FunctionLib.
352         * @param file FLD die geladen werden soll.
353         * @return FunctonLib
354         * @throws FunctionLibException
355         */
356        public static FunctionLib loadFromFile(Resource file) throws FunctionLibException       {
357                return loadFromFile(file,DEFAULT_SAX_PARSER);
358        }
359        
360        /**
361         * Laedt eine einzelne FunctionLib.
362         * @param res FLD die geladen werden soll.
363         * @param saxParser Definition des Sax Parser mit dem die FunctionLib eingelsesen werden soll.
364         * @return FunctionLib
365         * @throws FunctionLibException
366         */
367        public static FunctionLib loadFromFile(Resource res,String saxParser) throws FunctionLibException       {
368                // Read in XML
369                FunctionLib lib=FunctionLibFactory.hashLib.get(ResourceUtil.getCanonicalPathEL(res));//getHashLib(file.getAbsolutePath());
370                if(lib==null)   {
371                        lib=new FunctionLibFactory(saxParser,res).getLib();
372                        FunctionLibFactory.hashLib.put(ResourceUtil.getCanonicalPathEL(res),lib);
373                }
374                lib.setSource(res.toString());
375                
376                return lib;
377        }
378        
379        /**
380         * Laedt die Systeminterne FLD.
381         * @return FunctionLib
382         * @throws FunctionLibException
383         */
384        public static FunctionLib loadFromSystem() throws FunctionLibException  {
385                return loadFromSystem(DEFAULT_SAX_PARSER);
386        }
387        
388        /**
389         * Laedt die Systeminterne FLD.
390         * @param saxParser Definition des Sax Parser mit dem die FunctionLib eingelsesen werden soll.
391         * @return FunctionLib
392         * @throws FunctionLibException
393         */
394        public static FunctionLib loadFromSystem(String saxParser) throws FunctionLibException  {
395                if(systemFLD==null)
396                        systemFLD=new FunctionLibFactory(saxParser).getLib();
397                
398                return systemFLD;
399        }
400        
401        /**
402         * return one FunctionLib contain content of all given Function Libs
403         * @param flds
404         * @return combined function lib
405         */
406        public static FunctionLib combineFLDs(FunctionLib[] flds){
407                FunctionLib fl = new FunctionLib();
408                if(ArrayUtil.isEmpty(flds)) return fl ;
409
410                setAttributes(flds[0],fl);
411                
412                // add functions
413                for(int i=0;i<flds.length;i++){
414                        copyFunctions(flds[i],fl);
415                }
416                return fl;
417        }
418        
419        public static FunctionLib combineFLDs(Set flds){
420                FunctionLib newFL = new FunctionLib(),tmp;
421                if(flds.size()==0) return newFL ;
422
423                Iterator it = flds.iterator();
424                int count=0;
425                while(it.hasNext()){
426                        tmp=(FunctionLib) it.next();
427                        if(count++==0) setAttributes(tmp,newFL);
428                        copyFunctions(tmp,newFL);
429                }
430                return newFL;
431        }
432
433        /**
434         * copy function from one FunctionLib to a other
435         * @param extFL
436         * @param newFL
437         */
438        private static void copyFunctions(FunctionLib extFL, FunctionLib newFL) {
439                Iterator<Entry<String, FunctionLibFunction>> it = extFL.getFunctions().entrySet().iterator();
440                FunctionLibFunction flf;
441                while(it.hasNext()){
442                        flf= it.next().getValue(); // TODO function must be duplicated because it gets a new FunctionLib assigned
443                        newFL.setFunction(flf);
444                }
445        }
446
447        /**
448         * copy attributes from old fld to the new
449         * @param extFL
450         * @param newFL
451         */
452        private static void setAttributes(FunctionLib extFL, FunctionLib newFL) {
453                newFL.setDescription(extFL.getDescription());
454                newFL.setDisplayName(extFL.getDisplayName());
455                newFL.setShortName(extFL.getShortName());
456                newFL.setUri(extFL.getUri());
457                newFL.setVersion(extFL.getVersion());
458        }
459        
460}