001 /* 002 * Copyright (C) 2012 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 package org.crsh.lang.impl.groovy; 020 021 import groovy.lang.Closure; 022 import groovy.lang.GroovyShell; 023 import org.codehaus.groovy.ast.AnnotationNode; 024 import org.codehaus.groovy.ast.ClassNode; 025 import org.codehaus.groovy.ast.CompileUnit; 026 import org.codehaus.groovy.ast.MethodNode; 027 import org.codehaus.groovy.control.CompilationFailedException; 028 import org.codehaus.groovy.control.CompilationUnit; 029 import org.codehaus.groovy.control.CompilerConfiguration; 030 import org.codehaus.groovy.control.Phases; 031 import org.crsh.cli.Usage; 032 import org.crsh.cli.impl.descriptor.IntrospectionException; 033 import org.crsh.command.BaseCommand; 034 import org.crsh.lang.impl.java.ClassShellCommand; 035 import org.crsh.shell.ErrorKind; 036 import org.crsh.shell.impl.command.ShellSession; 037 import org.crsh.shell.impl.command.spi.Command; 038 import org.crsh.shell.impl.command.spi.CommandException; 039 import org.crsh.lang.impl.groovy.command.GroovyScriptShellCommand; 040 import org.crsh.lang.spi.CommandResolution; 041 import org.crsh.lang.impl.groovy.command.GroovyScriptCommand; 042 import org.crsh.plugin.PluginContext; 043 044 import java.io.UnsupportedEncodingException; 045 import java.util.Collections; 046 import java.util.Set; 047 import java.util.logging.Level; 048 import java.util.logging.Logger; 049 050 /** @author Julien Viet */ 051 public class GroovyCompiler implements org.crsh.lang.spi.Compiler { 052 053 /** . */ 054 static final Logger log = Logger.getLogger(GroovyCompiler.class.getName()); 055 056 /** . */ 057 private static final Set<String> EXT = Collections.singleton("groovy"); 058 059 /** . */ 060 private GroovyClassFactory<Object> objectGroovyClassFactory; 061 062 public GroovyCompiler(PluginContext context) { 063 this.objectGroovyClassFactory = new GroovyClassFactory<Object>(context.getLoader(), Object.class, GroovyScriptCommand.class); 064 } 065 066 public Set<String> getExtensions() { 067 return EXT; 068 } 069 070 public String doCallBack(ShellSession session, String name, String defaultValue) { 071 return eval(session, name, defaultValue); 072 } 073 074 /** 075 * The underlying groovu shell used for the REPL. 076 * 077 * @return a groovy shell operating on the session attributes 078 */ 079 public static GroovyShell getGroovyShell(ShellSession session) { 080 GroovyShell shell = (GroovyShell)session.get("shell"); 081 if (shell == null) { 082 CompilerConfiguration config = new CompilerConfiguration(); 083 config.setRecompileGroovySource(true); 084 ShellBinding binding = new ShellBinding(session, session); 085 shell = new GroovyShell(session.getContext().getLoader(), binding, config); 086 session.put("shell", shell); 087 } 088 return shell; 089 } 090 091 private String eval(ShellSession session, String name, String def) { 092 try { 093 GroovyShell shell = getGroovyShell(session); 094 Object ret = shell.getContext().getVariable(name); 095 if (ret instanceof Closure) { 096 log.log(Level.FINEST, "Invoking " + name + " closure"); 097 Closure c = (Closure)ret; 098 ret = c.call(); 099 } else if (ret == null) { 100 log.log(Level.FINEST, "No " + name + " will use empty"); 101 return def; 102 } 103 return String.valueOf(ret); 104 } 105 catch (Exception e) { 106 log.log(Level.SEVERE, "Could not get a " + name + " message, will use empty", e); 107 return def; 108 } 109 } 110 111 public CommandResolution compileCommand(final String name, byte[] source) throws CommandException, NullPointerException { 112 113 // 114 if (source == null) { 115 throw new NullPointerException("No null command source allowed"); 116 } 117 118 // 119 final String script; 120 try { 121 script = new String(source, "UTF-8"); 122 } 123 catch (UnsupportedEncodingException e) { 124 throw new CommandException(ErrorKind.INTERNAL, "Could not compile command script " + name, e); 125 } 126 127 // Get the description using a partial compilation because it is much faster than compiling the class 128 // the class will be compiled lazyly 129 String resolveDescription = null; 130 CompilationUnit cu = new CompilationUnit(objectGroovyClassFactory.config); 131 cu.addSource(name, script); 132 try { 133 cu.compile(Phases.CONVERSION); 134 } 135 catch (CompilationFailedException e) { 136 throw new CommandException(ErrorKind.INTERNAL, "Could not compile command", e); 137 } 138 CompileUnit ast = cu.getAST(); 139 if (ast.getClasses().size() > 0) { 140 ClassNode classNode= (ClassNode)ast.getClasses().get(0); 141 if (classNode != null) { 142 for (AnnotationNode annotation : classNode.getAnnotations()) { 143 if (annotation.getClassNode().getName().equals(Usage.class.getSimpleName())) { 144 resolveDescription = annotation.getMember("value").getText(); 145 break; 146 } 147 } 148 if (resolveDescription == null) { 149 for (MethodNode main : classNode.getMethods("main")) { 150 for (AnnotationNode annotation : main.getAnnotations()) { 151 if (annotation.getClassNode().getName().equals(Usage.class.getSimpleName())) { 152 resolveDescription = annotation.getMember("value").getText(); 153 break; 154 } 155 } 156 } 157 } 158 } 159 } 160 final String description = resolveDescription; 161 162 // 163 return new CommandResolution() { 164 Command<?> command; 165 @Override 166 public String getDescription() { 167 return description; 168 } 169 @Override 170 public Command<?> getCommand() throws CommandException { 171 if (command == null) { 172 Class<?> clazz = objectGroovyClassFactory.parse(name, script); 173 if (BaseCommand.class.isAssignableFrom(clazz)) { 174 Class<? extends BaseCommand> cmd = clazz.asSubclass(BaseCommand.class); 175 try { 176 command = make(cmd); 177 } 178 catch (IntrospectionException e) { 179 throw new CommandException(ErrorKind.INTERNAL, "Invalid cli annotations for command " + name, e); 180 } 181 } 182 else if (GroovyScriptCommand.class.isAssignableFrom(clazz)) { 183 Class<? extends GroovyScriptCommand> cmd = clazz.asSubclass(GroovyScriptCommand.class); 184 try { 185 command = make2(cmd); 186 } 187 catch (IntrospectionException e) { 188 throw new CommandException(ErrorKind.INTERNAL, "Invalid cli annotations for command " + name, e); 189 } 190 } 191 else { 192 throw new CommandException(ErrorKind.INTERNAL, "Could not create command " + name + " instance"); 193 } 194 } 195 return command; 196 } 197 }; 198 } 199 200 private <C extends BaseCommand> ClassShellCommand<C> make(Class<C> clazz) throws IntrospectionException { 201 return new ClassShellCommand<C>(clazz); 202 } 203 204 private <C extends GroovyScriptCommand> GroovyScriptShellCommand<C> make2(Class<C> clazz) throws IntrospectionException { 205 return new GroovyScriptShellCommand<C>(clazz); 206 } 207 }