/* * Copyright 2009-2016 the original author or authors. * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License 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. */ package org.codehaus.groovy.eclipse.dsl.script; import java.io.BufferedReader; import java.io.IOException; import java.io.InputStreamReader; import java.io.PrintWriter; import java.io.StringWriter; import java.util.Arrays; import java.util.Collection; import java.util.Iterator; import java.util.Map; import groovy.lang.Binding; import groovy.lang.Closure; import groovy.lang.GroovyClassLoader; import groovy.lang.MissingMethodException; import groovy.lang.Script; import org.codehaus.groovy.eclipse.GroovyLogManager; import org.codehaus.groovy.eclipse.TraceCategory; import org.codehaus.groovy.eclipse.dsl.GroovyDSLCoreActivator; import org.codehaus.groovy.eclipse.dsl.pointcuts.IPointcut; import org.eclipse.core.internal.resources.ResourceException; import org.eclipse.core.resources.IResourceStatus; import org.eclipse.core.resources.IStorage; import org.eclipse.core.runtime.CoreException; import org.eclipse.core.runtime.Platform; import org.eclipse.jdt.core.IJavaProject; import org.osgi.framework.Bundle; import org.osgi.framework.Version; /** * Executes a DSLD script and collects the results. * * @author andrew * @created Nov 17, 2010 */ public class DSLDScriptExecutor { private final class UnsupportedDSLVersion extends RuntimeException { private static final long serialVersionUID = 1L; public UnsupportedDSLVersion(String why) { super(scriptFile.getName() + " is not supported because:\n" + why); } } private final class RegisterClosure extends Closure<Object> { private static final long serialVersionUID = 1162731585734041055L; public RegisterClosure(Object owner) { super(owner); } @Override public Object call(Object arguments) { return tryRegister(arguments); } @Override public Object call(Object... arguments) { return tryRegister(arguments); } } private final class DSLDScriptBinding extends Binding { private final Script dsldScript; public DSLDScriptBinding(Script dsldScript) { this.dsldScript = dsldScript; } @Override public Object invokeMethod(String name, Object args) { if (name.equals("registerPointcut")) { return tryRegister(args); } else if (name.equals("supportsVersion")) { String result = (String) checkVersion(new Object[] {args}); return result == null; } else if (name.equals("assertVersion")) { String result = (String) checkVersion(new Object[] {args}); if (result != null) { throw new UnsupportedDSLVersion(result); } return null; } else if (name.equals("contribute")) { Object result = contribution(args); if (result == null) { throw new MissingMethodException(name, dsldScript.getClass(), new Object[] { args }); } return result; } else if (name.equals("log")) { if (GroovyLogManager.manager.hasLoggers()) { GroovyLogManager.manager.log(TraceCategory.DSL, "========== " + args); } return args; } IPointcut pc = factory.createPointcut(name); if (pc != null) { configure(pc, args); return pc; } else { return super.invokeMethod(name, args); } } @Override public Object getVariable(String name) { if ("registerPointcut".equals(name)) { return new RegisterClosure(this); } else if ("supportsVersion".equals(name)) { return new Closure<Object>(this) { private static final long serialVersionUID = 1L; @Override public Object call(Object... args) { String result = (String) checkVersion(args); return result == null; } }; } else if ("assertVersion".equals(name)) { return new Closure<Object>(this) { private static final long serialVersionUID = 1L; @Override public Object call(Object... args) { String result = (String) checkVersion(args); if (result != null) { throw new UnsupportedDSLVersion(result); } return null; } }; } else if ("contribute".equals(name)) { return new Closure<Object>(this) { private static final long serialVersionUID = 1L; @Override public Object call(Object... args) { Object result = contribution(args); if (result == null) { throw new MissingMethodException("contribute", dsldScript.getClass(), new Object[] { args }); } return result; } }; } else if ("log".equals(name)) { return new Closure<Object>(this) { private static final long serialVersionUID = 1L; @Override public Object call(Object... args) { if (GroovyLogManager.manager.hasLoggers()) { String msg; if (args == null) { msg = "null"; } else if (args.length == 0) { msg = ""; } else { msg = args[0].toString(); } GroovyLogManager.manager.log(TraceCategory.DSL, "========== " + msg); } return args; } }; } IPointcut pc = factory.createPointcut(name); if (pc != null) { return new PointcutClosure(this, pc); } else { return super.getVariable(name); } } @SuppressWarnings("unchecked") private void configure(IPointcut pointcut, Object arguments) { if (arguments instanceof Map) { for (Map.Entry<Object, Object> entry : ((Map<Object, Object>) arguments).entrySet()) { Object key = entry.getKey(); pointcut.addArgument(key == null ? null : key.toString(), entry.getValue()); } } else if (arguments instanceof Collection) { for (Object arg : (Collection<Object>) arguments) { pointcut.addArgument(arg); } } else if (arguments instanceof Object[]) { for (Object arg : (Object[]) arguments) { pointcut.addArgument(arg); } } else if (arguments != null) { pointcut.addArgument(arguments); } } } private final GroovyClassLoader gcl; private final IJavaProject project; private PointcutFactory factory; private IStorage scriptFile; public DSLDScriptExecutor(IJavaProject project) { // FIXADE Should have one classloader per project gcl = new GroovyClassLoader(GroovyDSLCoreActivator.class.getClassLoader()); this.project = project; } public Object executeScript(IStorage scriptFile) { this.scriptFile = scriptFile; String event = null; try { if (GroovyLogManager.manager.hasLoggers()) { GroovyLogManager.manager.log(TraceCategory.DSL, "About to compile script for " + scriptFile); event = "Script creation for " + scriptFile; GroovyLogManager.manager.logStart(event); } factory = new PointcutFactory(scriptFile, project.getProject()); Object result = null; try { String scriptContents = getContents(scriptFile); Class<Script> clazz = null; try { clazz = gcl.parseClass(scriptContents, scriptFile.getName()); } catch (Exception e) { if (GroovyLogManager.manager.hasLoggers()) { StringWriter writer = new StringWriter(); e.printStackTrace(new PrintWriter(writer)); GroovyLogManager.manager.log(TraceCategory.DSL, "Attempted to compile " + scriptFile + "but failed because:\n" + writer.getBuffer()); } return result; } if (!Script.class.isAssignableFrom(clazz)) { // might be some strange compile error // or a class is accidentally defined if (GroovyLogManager.manager.hasLoggers()) { GroovyLogManager.manager.log(TraceCategory.DSL, "Class " + scriptFile + " is not a script. Can't execute as DSLD."); } return result; } Script dsldScript = clazz.newInstance(); dsldScript.setBinding(new DSLDScriptBinding(dsldScript)); result = dsldScript.run(); } catch (UnsupportedDSLVersion e) { if (GroovyLogManager.manager.hasLoggers()) { GroovyLogManager.manager.log(TraceCategory.DSL, e.getMessage()); } } catch (Exception e) { // log this exception both to the event logger and to the error log GroovyDSLCoreActivator.logException(e); } return result; } finally { if (event != null) { GroovyLogManager.manager.logEnd(event, TraceCategory.DSL); } } } public String getContents(IStorage file) throws IOException, CoreException { BufferedReader br; try { br = new BufferedReader(new InputStreamReader(file.getContents())); } catch (ResourceException e) { if (e.getStatus().getCode() == IResourceStatus.RESOURCE_NOT_FOUND) { // ignore...probably not able to access an external file } else { throw e; } return ""; } StringBuffer sb= new StringBuffer(300); try { int read= 0; while ((read= br.read()) != -1) sb.append((char) read); } finally { br.close(); } return sb.toString(); } protected Object tryRegister(Object args) { Object[] nameAndClosure = extractArgsForRegister(args); if (nameAndClosure != null) { factory.registerLocalPointcut((String) nameAndClosure[0], (Closure<?>) nameAndClosure[1]); return nameAndClosure[1]; } else { if (GroovyLogManager.manager.hasLoggers()) { GroovyLogManager.manager.log(TraceCategory.DSL, "Cannot register custom pointcut for " + (args instanceof Object[] ? Arrays.toString((Object[]) args) : args)); } return null; } } @SuppressWarnings("unchecked") protected Object[] extractArgsContribution(Object args) { if (args instanceof Object[]) { Object[] arr = (Object[]) args; if (arr.length == 2 && arr[0] instanceof IPointcut && arr[1] instanceof Closure) { return arr; } } else if (args instanceof Collection) { Collection<Object> coll = (Collection<Object>) args; Object[] arr = new Object[2]; Iterator<Object> iter = coll.iterator(); if (iter.hasNext() && (arr[0] = iter.next()) instanceof IPointcut && iter.hasNext() && (arr[1] = iter.next()) instanceof Closure && !iter.hasNext()) { return arr; } } else if (args instanceof Map) { return extractArgsContribution(((Map<Object, Object>) args).values()); } return null; } @SuppressWarnings("unchecked") protected Object[] extractArgsForRegister(Object args) { if (args instanceof Object[]) { Object[] arr = (Object[]) args; if (arr.length == 2 && arr[0] instanceof String && arr[1] instanceof Closure) { return arr; } } else if (args instanceof Collection) { Collection<Object> coll = (Collection<Object>) args; Object[] arr = new Object[2]; Iterator<Object> iter = coll.iterator(); if (iter.hasNext() && (arr[0] = iter.next()) instanceof String && iter.hasNext() && (arr[1] = iter.next()) instanceof Closure && !iter.hasNext()) { return arr; } } else if (args instanceof Map) { return extractArgsForRegister(((Map<Object, Object>) args).values()); } return null; } private static Version groovyEclipseVersion; private static Version groovyVersion; private static Version grailsToolingVersion; private final static Object versionLock = new Object(); private static void initializeVersions() { groovyEclipseVersion = GroovyDSLCoreActivator.getDefault().getBundle().getVersion(); Bundle groovyBundle = Platform.getBundle("org.codehaus.groovy"); if (groovyBundle != null) { groovyVersion = groovyBundle.getVersion(); } Bundle grailsBundle = Platform.getBundle("com.springsource.sts.grails.core"); if (grailsBundle == null) { grailsBundle = Platform.getBundle("org.grails.ide.eclipse.core"); } if (grailsBundle != null) { grailsToolingVersion = grailsBundle.getVersion(); } } /** * synonym for IPointcut.accept() */ public Object contribution(Object args) { Object[] contributionArgs = extractArgsContribution(args); if (args == null || contributionArgs.length == 0) { return null; } IPointcut p = (IPointcut) contributionArgs[0]; p.accept((Closure<?>) contributionArgs[1]); return Boolean.TRUE; } public Object checkVersion(Object[] array) { if (array == null || array.length != 1) { return createInvalidVersionString(array); } Object args = array[0]; synchronized(versionLock) { if (groovyEclipseVersion == null) { initializeVersions(); } } if (! (args instanceof Map)) { return createInvalidVersionString(args); } Map<?,?> versions = (Map<?,?>) args; for (Map.Entry<?,?> entry : versions.entrySet()) { if (! (entry.getValue() instanceof String)) { return createInvalidVersionString(args); } Version v = null; try { v = new Version((String) entry.getValue()); } catch (IllegalArgumentException e) { throw new UnsupportedDSLVersion(e.getMessage()); } if ("groovy".equals(entry.getKey())) { if (groovyVersion != null && v.compareTo(groovyVersion) > 0) { return "Invalid Groovy version. Expected: " + v + " Installed: " + groovyVersion; } else if (groovyVersion == null) { return "Could not find a Groovy version. Expected: " + groovyVersion; } } else if ("groovyEclipse".equals(entry.getKey())) { if (groovyEclipseVersion != null && v.compareTo(groovyEclipseVersion) > 0) { return "Invalid Groovy-Eclipse version. Expected: " + v + " Installed: " + groovyEclipseVersion; } else if (groovyEclipseVersion == null) { return "Could not find a Groovy-Eclipse version. Expected: " + groovyEclipseVersion; } } else if ("grailsTooling".equals(entry.getKey()) || "sts".equals(entry.getKey())) { if (grailsToolingVersion != null && v.compareTo(grailsToolingVersion) > 0) { return "Invalid Grails Tooling version. Expected: " + v + " Installed: " + grailsToolingVersion; } else if (grailsToolingVersion == null) { return "Could not find a Grails Tooling version. Expected: " + grailsToolingVersion; } } else { return createInvalidVersionString(args); } } return null; } protected String createInvalidVersionString(Object args) { return args + " is not a valid version identifier, must be a Map<String, String>. " + "Each value must be a version number X.Y.Z. " + "Supported version checking is: 'groovy', 'grailsTooling', 'groovyEclipse'."; } }