001package com.killcoding.jsplus;
002
003import java.io.File;
004import java.io.InputStream;
005import java.io.FileInputStream;
006import com.killcoding.file.BaseFile;
007import java.util.Scanner;
008import java.io.IOException;
009import java.nio.charset.Charset;
010import java.io.ByteArrayInputStream;
011import com.killcoding.jsplus.JsPlusContext;
012import com.killcoding.jsplus.JsContext;
013import com.killcoding.jsplus.GraalVmContext;
014import java.util.regex.Pattern;
015import java.util.regex.Matcher;
016import com.killcoding.log.LoggerFactory;
017import com.killcoding.log.Logger;
018import com.killcoding.log.LogRecord;
019import java.util.Map;
020import java.util.HashMap;
021import com.killcoding.tool.CommonTools;
022import java.util.Set;
023import com.killcoding.file.DiskFile;
024import java.util.concurrent.ConcurrentHashMap;
025
026public class TextScript {
027    
028    public static String DEFAULT_ENGINE_NAME = "nashorn";
029    
030    protected boolean allowAllAccess = true;
031    
032    protected final static Map<String,JsPlusContext> CONTEXT_MAPPING = new ConcurrentHashMap<String,JsPlusContext>();
033    
034    protected String prefix = "#";
035    
036    protected String varName = "theContent";
037
038    protected JsPlusContext context = null;
039    
040    protected Scanner scanner = null;
041    
042    public TextScript() throws Exception{
043        super();
044    }
045    
046    public TextScript(File scriptPath) throws Exception{
047        super();
048        
049        if(scriptPath == null) throw new NullPointerException("The 'scriptPath' is null.");
050
051        byte[] scriptBytes = new DiskFile(scriptPath).readAllBytes();
052        scanner = new Scanner(new ByteArrayInputStream(scriptBytes), BaseFile.CHARSET);
053    }
054    
055    public TextScript(String script) throws Exception{
056        super();
057        
058        if(script == null) throw new NullPointerException("The 'script' is null.");
059        
060        byte[] scriptBytes = script.getBytes(BaseFile.CHARSET);
061        scanner = new Scanner(new ByteArrayInputStream(scriptBytes), BaseFile.CHARSET);
062    }  
063    
064    protected boolean isEnd(String line){
065            return isMatched(getEndRegex(),line);
066    } 
067    
068    protected String parseEnd(String line){
069            Pattern pattern = Pattern.compile(getEndRegex());
070        Matcher matcher = pattern.matcher(line);
071        Map<String,String> fullMapping = new HashMap<String,String>();
072        Map<String,String> scriptMapping = new HashMap<String,String>();
073        StringBuffer sb = new StringBuffer();
074        while(matcher.find()){
075            String full = matcher.group(1);
076            String script = matcher.group(2);
077            String replaceToken = "#" + CommonTools.generateId(16) + "#";
078            matcher.appendReplacement(sb,replaceToken);
079            fullMapping.put(replaceToken,full);
080            scriptMapping.put(replaceToken,script);
081        }
082        matcher.appendTail(sb);
083        String quoteLine = sb.toString();
084        Set<String> tokens = fullMapping.keySet();
085        for(String token : tokens){
086            quoteLine = quoteLine.replaceAll(token," } ");
087        }
088        return quoteLine;
089    } 
090
091    protected boolean isBlock(String line){
092            return isMatched(getBlockRegex(),line);
093    }  
094    
095    protected boolean isDefault(String line){
096        return isMatched(getDefaultRegex(),line);
097    }  
098    
099    protected String parseDefault(String line){
100            Pattern pattern = Pattern.compile(getDefaultRegex());
101        Matcher matcher = pattern.matcher(line);
102        Map<String,String> fullMapping = new HashMap<String,String>();
103        Map<String,String> scriptMapping = new HashMap<String,String>();
104        StringBuffer sb = new StringBuffer();
105        while(matcher.find()){
106            String full = matcher.group(1);
107            String script = matcher.group(2);
108            String replaceToken = "#" + CommonTools.generateId(16) + "#";
109            matcher.appendReplacement(sb,replaceToken);
110            fullMapping.put(replaceToken,full);
111            scriptMapping.put(replaceToken,script);
112        }
113        matcher.appendTail(sb);
114        String quoteLine = sb.toString();
115        Set<String> tokens = fullMapping.keySet();
116        for(String token : tokens){
117            quoteLine = quoteLine.replaceAll(token, scriptMapping.get(token).replaceAll(":","").trim() + ": ");
118        }
119        return quoteLine;
120    }     
121    
122    protected boolean isCase(String line){
123        return isMatched(getCaseRegex(),line);
124    }  
125    
126    protected String parseCase(String line){
127            Pattern pattern = Pattern.compile(getCaseRegex());
128        Matcher matcher = pattern.matcher(line);
129        Map<String,String> fullMapping = new HashMap<String,String>();
130        Map<String,String> scriptMapping = new HashMap<String,String>();
131        StringBuffer sb = new StringBuffer();
132        while(matcher.find()){
133            String full = matcher.group(1);
134            String script = matcher.group(2);
135            String replaceToken = "#" + CommonTools.generateId(16) + "#";
136            matcher.appendReplacement(sb,replaceToken);
137            fullMapping.put(replaceToken,full);
138            scriptMapping.put(replaceToken,script);
139        }
140        matcher.appendTail(sb);
141        String quoteLine = sb.toString();
142        Set<String> tokens = fullMapping.keySet();
143        for(String token : tokens){
144            quoteLine = quoteLine.replaceAll(token, scriptMapping.get(token));
145        }
146        return quoteLine;
147    }    
148    
149    protected boolean isCode(String line){
150        return isMatched(getCodeRegex(),line);
151    }  
152    
153    protected String parseCode(String line){
154            Pattern pattern = Pattern.compile(getCodeRegex());
155        Matcher matcher = pattern.matcher(line);
156        Map<String,String> fullMapping = new HashMap<String,String>();
157        Map<String,String> scriptMapping = new HashMap<String,String>();
158        StringBuffer sb = new StringBuffer();
159        while(matcher.find()){
160            String full = matcher.group(1);
161            String script = matcher.group(2);
162            String replaceToken = "#" + CommonTools.generateId(16) + "#";
163            matcher.appendReplacement(sb,replaceToken);
164            fullMapping.put(replaceToken,full);
165            scriptMapping.put(replaceToken,script);
166        }
167        matcher.appendTail(sb);
168        String quoteLine = sb.toString();
169        Set<String> tokens = fullMapping.keySet();
170        for(String token : tokens){
171            quoteLine = quoteLine.replaceAll(token, scriptMapping.get(token));
172        }
173        return quoteLine;
174    }       
175        
176    
177    protected boolean isJump(String line){
178        return isMatched(getJumpRegex(),line);
179    }  
180    
181    protected String parseJump(String line){
182            Pattern pattern = Pattern.compile(getJumpRegex());
183        Matcher matcher = pattern.matcher(line);
184        Map<String,String> fullMapping = new HashMap<String,String>();
185        Map<String,String> scriptMapping = new HashMap<String,String>();
186        StringBuffer sb = new StringBuffer();
187        while(matcher.find()){
188            String full = matcher.group(1);
189            String script = matcher.group(2);
190            String replaceToken = "#" + CommonTools.generateId(16) + "#";
191            matcher.appendReplacement(sb,replaceToken);
192            fullMapping.put(replaceToken,full);
193            scriptMapping.put(replaceToken,script);
194        }
195        matcher.appendTail(sb);
196        String quoteLine = sb.toString();
197        Set<String> tokens = fullMapping.keySet();
198        for(String token : tokens){
199            quoteLine = quoteLine.replaceAll(token, scriptMapping.get(token) + ";");
200        }
201        return quoteLine;
202    }       
203    
204    protected boolean isElseIf(String line){
205        return isMatched(getElseIfRegex(),line);
206    }  
207    
208    protected String parseElseIf(String line){
209            Pattern pattern = Pattern.compile(getElseIfRegex());
210        Matcher matcher = pattern.matcher(line);
211        Map<String,String> fullMapping = new HashMap<String,String>();
212        Map<String,String> scriptMapping = new HashMap<String,String>();
213        StringBuffer sb = new StringBuffer();
214        while(matcher.find()){
215            String full = matcher.group(1);
216            String script = matcher.group(2);
217            String replaceToken = "#" + CommonTools.generateId(16) + "#";
218            matcher.appendReplacement(sb,replaceToken);
219            fullMapping.put(replaceToken,full);
220            scriptMapping.put(replaceToken,script);
221        }
222        matcher.appendTail(sb);
223        String quoteLine = sb.toString();
224        Set<String> tokens = fullMapping.keySet();
225        for(String token : tokens){
226            quoteLine = quoteLine.replaceAll(token, " } " + scriptMapping.get(token) + " { ");
227        }
228        return quoteLine;
229    }     
230    
231    protected boolean isElse(String line){
232        return isMatched(getElseRegex(),line);
233    }  
234    
235    protected String parseElse(String line){
236            Pattern pattern = Pattern.compile(getElseRegex());
237        Matcher matcher = pattern.matcher(line);
238        Map<String,String> fullMapping = new HashMap<String,String>();
239        Map<String,String> scriptMapping = new HashMap<String,String>();
240        StringBuffer sb = new StringBuffer();
241        while(matcher.find()){
242            String full = matcher.group(1);
243            String script = matcher.group(2);
244            String replaceToken = "#" + CommonTools.generateId(16) + "#";
245            matcher.appendReplacement(sb,replaceToken);
246            fullMapping.put(replaceToken,full);
247            scriptMapping.put(replaceToken,script);
248        }
249        matcher.appendTail(sb);
250        String quoteLine = sb.toString();
251        Set<String> tokens = fullMapping.keySet();
252        for(String token : tokens){
253            quoteLine = quoteLine.replaceAll(token, " } " + scriptMapping.get(token) + " { ");
254        }
255        return quoteLine;
256    }      
257    
258    protected boolean isComment(String line){
259            return isMatched(getCommentRegex(),line);
260    }   
261    
262    protected boolean isLogic(String line){
263            return isMatched(getLogicRegex(),line);
264    }    
265    
266    protected String parseLogic(String line){
267            Pattern pattern = Pattern.compile(getLogicRegex());
268        Matcher matcher = pattern.matcher(line);
269        Map<String,String> fullMapping = new HashMap<String,String>();
270        Map<String,String> scriptMapping = new HashMap<String,String>();
271        StringBuffer sb = new StringBuffer();
272        while(matcher.find()){
273            String full = matcher.group(1);
274            String script = matcher.group(2);
275            String replaceToken = "#" + CommonTools.generateId(16) + "#";
276            matcher.appendReplacement(sb,replaceToken);
277            fullMapping.put(replaceToken,full);
278            scriptMapping.put(replaceToken,script);
279        }
280        matcher.appendTail(sb);
281        String quoteLine = sb.toString();
282        Set<String> tokens = fullMapping.keySet();
283        for(String token : tokens){
284            quoteLine = quoteLine.replaceAll(token, scriptMapping.get(token) + " { ");
285        }
286        return quoteLine;
287    }    
288    
289    protected boolean isExpression(String line){
290           return isMatched(getExpressionRegex(),line);
291    }
292    
293    protected boolean isMatched(String regex,String line){
294            Pattern pattern = Pattern.compile(regex);
295        Matcher matcher = pattern.matcher(line);
296        return matcher.find();
297    }    
298    
299    protected String parseExpression(String line){
300            Pattern pattern = Pattern.compile(getExpressionRegex());
301        Matcher matcher = pattern.matcher(line);
302        Map<String,String> fullMapping = new HashMap<String,String>();
303        Map<String,String> scriptMapping = new HashMap<String,String>();
304        StringBuffer sb = new StringBuffer();
305        while(matcher.find()){
306            String full = matcher.group(1);
307            String script = matcher.group(2);
308            String replaceToken = "#" + CommonTools.generateId(16) + "#";
309            matcher.appendReplacement(sb,replaceToken);
310            fullMapping.put(replaceToken,full);
311            scriptMapping.put(replaceToken,script);
312        }
313        matcher.appendTail(sb);
314        String quoteLine = parseRawText(sb.toString());
315        Set<String> tokens = fullMapping.keySet();
316        for(String token : tokens){
317            quoteLine = quoteLine.replaceAll(token,"' + " + scriptMapping.get(token) + " + '");
318        }
319        return quoteLine;
320    }    
321    
322    protected String parseRawText(String line){
323        String quoteLine = line.replaceAll("'","\\\\'");
324        return "'" + quoteLine + "'";
325    }  
326    
327    public String parse() throws Exception {
328        return parse(false);
329    }
330    
331    public String parse(boolean showScript) throws Exception {
332        boolean isWin = System.getProperty("os.name").toUpperCase().contains("WINDOWS");
333        StringBuffer blockScript = new StringBuffer();
334        StringBuffer all = new StringBuffer();
335        boolean haveBlock = false;
336        try{
337                while (scanner.hasNextLine()) {
338                    boolean needlineBreak = false;
339                    String line = scanner.nextLine();
340                    
341                    if(!haveBlock && isBlock(line)){
342                        haveBlock = true;
343                    }
344                    
345                    if(haveBlock && !isBlock(line) && !isEnd(line)){
346                        blockScript.append(line);
347                        
348                        if(isWin) blockScript.append("\r\n");
349                    
350                    if(!isWin) blockScript.append("\n");
351                    
352                    continue;
353                    
354                    }
355                    
356                    if(haveBlock) {
357                        haveBlock = !isEnd(line);
358                        
359                        if(!haveBlock) continue;
360                    }
361                    
362                    if(isBlock(line)) continue;
363     
364                    if(isComment(line)) continue;
365                    
366                if(isExpression(line)){
367                    String parseLine = parseExpression(line);
368                    all.append(varName + " += " + parseLine + ";");
369                    needlineBreak = true;
370                }else if(isLogic(line)){
371                    all.append(parseLogic(line));
372                    needlineBreak = false;
373                }else if(isElse(line)){
374                    all.append(parseElse(line));
375                    needlineBreak = false;
376                }else if(isElseIf(line)){
377                    all.append(parseElseIf(line));
378                    needlineBreak = false;
379                }else if(isJump(line)){
380                    all.append(parseJump(line));
381                    needlineBreak = false;
382                }else if(isCode(line)){
383                    all.append(parseCode(line));
384                    needlineBreak = false;
385                }else if(isCase(line)){
386                    all.append(parseCase(line));
387                    needlineBreak = false;
388                }else if(isDefault(line)){
389                    all.append(parseDefault(line));
390                    needlineBreak = false;
391                }else if(isEnd(line)){
392                    all.append(parseEnd(line));
393                    needlineBreak = false;
394                }else{
395                    all.append(varName + " += " + parseRawText(line) + ";");
396                    needlineBreak = true;
397                }
398                
399                if(!needlineBreak){
400                    if(isWin) all.append("\r\n");
401                    
402                    if(!isWin) all.append("\n");
403                }              
404                
405                if(needlineBreak && scanner.hasNextLine()){
406                    if(isWin) all.append(varName + " += '\\r\\n';\r\n");
407                    
408                    if(!isWin) all.append(varName + " += '\\n';\n");
409                }            
410                }
411        }finally{
412            scanner.close();
413        }
414        String allStr = all.toString();
415        String functionScript = String.format("(function(){%s var %s = '';%s return %s;})()",blockScript.toString(),varName,allStr,varName);
416        if(showScript){
417            return functionScript;
418        }else{
419            if(context == null){
420                useEngine(DEFAULT_ENGINE_NAME);
421            }
422            return context.evalScript(functionScript).toString();
423        }
424    }
425    
426    public Object evalScript(String script) throws Exception {
427        if(context == null){
428            useEngine(DEFAULT_ENGINE_NAME);
429        }
430            return context.evalScript(script);
431    }
432    
433    public Object eval(File scriptPath) throws Exception {
434        if(context == null){
435            useEngine(DEFAULT_ENGINE_NAME);
436        }
437            return context.eval(scriptPath);
438    }    
439    
440    public TextScript addParams(Map<String,Object> params) throws Exception {
441        if(context == null){
442            useEngine(DEFAULT_ENGINE_NAME);
443        }        
444        Set<String> keys = params.keySet();
445        for(String key : keys){
446            this.context.put(key,params.get(key));
447        }
448        return this;
449    }
450    
451    public TextScript useEngine(String engineName) throws Exception {
452            this.context = CONTEXT_MAPPING.get(engineName);
453            if(this.context == null){
454                TextScript.initEngine(allowAllAccess,engineName);
455                this.context = CONTEXT_MAPPING.get(engineName);
456            if(this.context == null){
457                TextScript.initEngine(allowAllAccess,engineName);
458                throw new Exception("The context is null, please init engine (e.g. TextScript.initEngine(engineName)).");
459            }           
460            }
461                    
462            return this;
463    }      
464    
465    public static boolean initEngine(String engineName) throws Exception {   
466        return initEngine(true,engineName);
467    }
468    
469    public static boolean initEngine(boolean allowAllAccess,String engineName) throws Exception {
470        JsPlusContext _context = CONTEXT_MAPPING.get(engineName);
471        if(_context == null){
472            boolean isGraalJs = JsPlusContext.isGraalJs(engineName);
473            boolean isNashornJs = JsPlusContext.isNashornJs(engineName);
474            if(isGraalJs){
475                _context = new GraalVmContext(engineName);
476            }else if(isNashornJs){
477                _context = new NashornContext(engineName);
478                }else{
479                _context = new JsContext(engineName);
480            }
481            _context.setAllowAllAccess(allowAllAccess);
482            _context.initScriptEngineManager();
483            CONTEXT_MAPPING.put(engineName,_context);
484            return true;
485        }else{
486            Logger.systemDebug(TextScript.class,"This engine '{}' already exists.",engineName);
487            return false;
488        }
489    }
490    
491    public JsPlusContext getContext() throws Exception {
492        if(context == null){
493            useEngine(DEFAULT_ENGINE_NAME);
494        }        
495        return context;
496    }
497    
498    public void setPrefix(String prefix){
499        this.prefix = prefix;   
500    }
501    
502    public String getPrefix(){
503        return prefix;
504    }
505    
506    public void setVarName(String varName){
507        this.varName = varName;   
508    }
509    
510    public String getVarName(){
511        return varName;
512    }    
513    
514    protected String getExpressionRegex(){
515        return "(" + getPrefix() + "\\{[^\\S]*([^\\}]*)[^\\S]*\\})";
516    }
517    
518    protected String getLogicRegex(){
519        return "([^\\S]*" + getPrefix() +"((if|switch|for|do|while){1}[^\\S]*\\([^\\S]*(.*))[^\\S]*)";
520    }    
521    
522    protected String getElseRegex(){
523        return "([^\\S]*" + getPrefix() + "(else){1}[^\\S]*)$";
524    }      
525    
526    protected String getElseIfRegex(){
527        return "([^\\S]*" + getPrefix() + "((else[^\\S]+if){1}[^\\S]*\\([^\\S]*(.*))[^\\S]*)";
528    } 
529   
530    protected String getEndRegex(){
531        return "([^\\S]*" + getPrefix() +"(end){1}[^\\S]*)$";
532    }  
533
534    protected String getJumpRegex(){
535        return "([^\\S]*" + getPrefix() + "(break|continue){1}[^\\S]*)$";
536    }  
537
538    protected String getCaseRegex(){
539        return "([^\\S]*" + getPrefix() + "(case[^\\S]+.*))";
540    }  
541    
542    protected String getDefaultRegex(){
543        return "([^\\S]*" + getPrefix() + "(default[^\\S]*[:]?)[^\\S|$]?)";    
544    }      
545
546    protected String getCodeRegex(){
547        return "([^\\S]*" + getPrefix() + "code[^\\S]+(.*))"; 
548    }   
549    
550    protected String getCommentRegex(){
551        return "([^\\S]*" + getPrefix() + "comment[^\\S]+(.*))"; 
552    }      
553    
554    protected String getBlockRegex(){
555        return "([^\\S]*" + getPrefix() +"(block){1}[^\\S]*)$";
556    }  
557    
558    public void setAllowAllAccess(boolean allowAllAccess){
559        this.allowAllAccess = allowAllAccess;
560    }
561    
562    public boolean isAllowAllAccess(){
563        return this.allowAllAccess;
564    }
565    
566}