001    /*
002     * Copyright (C) 2010 eXo Platform SAS.
003     *
004     * This is free software; you can redistribute it and/or modify it
005     * under the terms of the GNU Lesser General Public License as
006     * published by the Free Software Foundation; either version 2.1 of
007     * the License, or (at your option) any later version.
008     *
009     * This software is distributed in the hope that it will be useful,
010     * but WITHOUT ANY WARRANTY; without even the implied warranty of
011     * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
012     * Lesser General Public License for more details.
013     *
014     * You should have received a copy of the GNU Lesser General Public
015     * License along with this software; if not, write to the Free
016     * Software Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA
017     * 02110-1301 USA, or see the FSF site: http://www.fsf.org.
018     */
019    
020    package org.crsh.shell.impl.command;
021    
022    import org.crsh.command.CommandInvoker;
023    import org.crsh.command.NoSuchCommandException;
024    import org.crsh.command.ScriptException;
025    import org.crsh.command.ShellCommand;
026    import org.crsh.shell.ErrorType;
027    import org.crsh.shell.ShellResponse;
028    import org.crsh.shell.ShellProcessContext;
029    import org.crsh.text.CharReader;
030    import org.crsh.text.Style;
031    
032    import java.util.ArrayList;
033    import java.util.regex.Pattern;
034    
035    /**
036     * @author <a href="mailto:julien.viet@exoplatform.com">Julien Viet</a>
037     * @version $Revision$
038     */
039    abstract class AST {
040    
041      abstract Term lastTerm();
042    
043      static class Expr extends AST {
044    
045        /** . */
046        final Term term;
047    
048        /** . */
049        final Expr next;
050    
051        Expr(Term term) {
052          this.term = term;
053          this.next = null;
054        }
055    
056        Expr(Term term, Expr next) {
057          this.term = term;
058          this.next = next;
059        }
060    
061        final CRaSHProcess create(CRaSHSession crash, String request) throws NoSuchCommandException {
062          term.create(crash);
063          if (next != null) {
064            next.create(crash);
065          }
066          return new CRaSHProcess(crash, request) {
067            @Override
068            ShellResponse doInvoke(ShellProcessContext context) throws InterruptedException {
069              return Expr.this.execute(crash, context, null);
070            }
071          };
072        }
073    
074        private void create(CRaSHSession crash) throws NoSuchCommandException {
075          term.create(crash);
076          if (next != null) {
077            next.create(crash);
078          }
079        }
080    
081        protected ShellResponse execute(CRaSHSession crash, ShellProcessContext context, ArrayList consumed) throws InterruptedException {
082    
083          // What will be produced by this expression
084          ArrayList produced = new ArrayList();
085    
086          //
087          CharReader reader = new CharReader();
088    
089          // Iterate over all terms
090          for (Term current = term;current != null;current = current.next) {
091    
092            // Build command context
093            InvocationContextImpl ctx;
094            if (current.invoker.getConsumedType() == Void.class) {
095              ctx = new InvocationContextImpl(context, null, crash.attributes, crash.crash.getContext().getAttributes());
096            } else {
097              // For now we assume we have compatible consumed/produced types
098              ctx = new InvocationContextImpl(context, consumed, crash.attributes, crash.crash.getContext().getAttributes());
099            }
100    
101            //
102            try {
103              current.invoker.invoke(ctx);
104            } catch (ScriptException e) {
105    
106              // Should we handle InterruptedException here ?
107    
108              return current.build(e);
109            } catch (Throwable t) {
110              return current.build(t);
111            }
112    
113            // Append anything that was in the buffer
114            CharReader ctxReader = ctx.getReader();
115            if (ctxReader != null && !ctxReader.isEmpty()) {
116              reader.append(ctxReader).append(Style.reset);
117            }
118    
119            // Append produced if possible
120            if (current.invoker.getProducedType() == Void.class) {
121              // Do nothing
122            } else {
123              produced.addAll(ctx.getProducedItems());
124            }
125          }
126    
127          //
128          if (next != null) {
129            return next.execute(crash, context, produced);
130          } else {
131            ShellResponse response;
132            if (!reader.isEmpty()) {
133              response = ShellResponse.display(produced, reader);
134            } else {
135              response = ShellResponse.ok(produced);
136            }
137            return response;
138          }
139        }
140    
141        @Override
142        Term lastTerm() {
143          if (next != null) {
144            return next.lastTerm();
145          }
146          if (term != null) {
147            return term.lastTerm();
148          }
149          return null;
150        }
151      }
152    
153      static class Term extends AST {
154    
155        /** . */
156        final String line;
157    
158        /** . */
159        final Term next;
160    
161        /** . */
162        final String name;
163    
164        /** . */
165        final String rest;
166    
167        /** . */
168        private ShellCommand command;
169    
170        /** . */
171        private CommandInvoker invoker;
172    
173        Term(String line) {
174          this(line, null);
175        }
176    
177        Term(String line, Term next) {
178    
179          Pattern p = Pattern.compile("^\\s*(\\S+)");
180          java.util.regex.Matcher m = p.matcher(line);
181          String name = null;
182          String rest = null;
183          if (m.find()) {
184            name = m.group(1);
185            rest = line.substring(m.end());
186          }
187    
188          //
189          this.name = name;
190          this.rest = rest;
191          this.line = line;
192          this.next = next;
193        }
194    
195        private void create(CRaSHSession session) throws NoSuchCommandException {
196          CommandInvoker invoker = null;
197          if (name != null) {
198            command = session.crash.getCommand(name);
199            if (command != null) {
200              invoker = command.createInvoker(rest);
201            }
202          }
203    
204          //
205          if (invoker == null) {
206            throw new NoSuchCommandException(name);
207          } else {
208            this.invoker = invoker;
209          }
210    
211          //
212          if (next != null) {
213            next.create(session);
214          }
215        }
216    
217        String getLine() {
218          return line;
219        }
220    
221        @Override
222        Term lastTerm() {
223          if (next != null) {
224            return next.lastTerm();
225          } else {
226            return this;
227          }
228        }
229    
230        private ShellResponse.Error build(Throwable throwable) {
231          ErrorType errorType;
232          if (throwable instanceof ScriptException) {
233            errorType = ErrorType.EVALUATION;
234            Throwable cause = throwable.getCause();
235            if (cause != null) {
236              throwable = cause;
237            }
238          } else {
239            errorType = ErrorType.INTERNAL;
240          }
241          String result;
242          String msg = throwable.getMessage();
243          if (throwable instanceof ScriptException) {
244            if (msg == null) {
245              result = name + ": failed";
246            } else {
247              result = name + ": " + msg;
248            }
249            return ShellResponse.error(errorType, result, throwable);
250          } else {
251            if (msg == null) {
252              msg = throwable.getClass().getSimpleName();
253            }
254            if (throwable instanceof RuntimeException) {
255              result = name + ": exception: " + msg;
256            } else if (throwable instanceof Exception) {
257              result = name + ": exception: " + msg;
258            } else if (throwable instanceof java.lang.Error) {
259              result = name + ": error: " + msg;
260            } else {
261              result = name + ": unexpected throwable: " + msg;
262            }
263            return ShellResponse.error(errorType, result, throwable);
264          }
265        }
266      }
267    }