http://git-wip-us.apache.org/repos/asf/groovy/blob/10110145/src/main/groovy/groovy/transform/stc/FromString.java ---------------------------------------------------------------------- diff --git a/src/main/groovy/groovy/transform/stc/FromString.java b/src/main/groovy/groovy/transform/stc/FromString.java new file mode 100644 index 0000000..12f9371 --- /dev/null +++ b/src/main/groovy/groovy/transform/stc/FromString.java @@ -0,0 +1,80 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one + * or more contributor license agreements. See the NOTICE file + * distributed with this work for additional information + * regarding copyright ownership. The ASF licenses this file + * to you 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 groovy.transform.stc; + +import org.codehaus.groovy.ast.ASTNode; +import org.codehaus.groovy.ast.ClassNode; +import org.codehaus.groovy.ast.MethodNode; +import org.codehaus.groovy.ast.tools.GenericsUtils; +import org.codehaus.groovy.control.CompilationUnit; +import org.codehaus.groovy.control.SourceUnit; + +import java.util.ArrayList; +import java.util.List; + + +/** + * <p>A closure parameter hint class that is convenient if you want to use a String representation + * of the signature. It makes use of the {@link ClosureParams#options() option strings}, where + * each string corresponds to a single signature.</p> + * + * <p>The following example describes a closure as accepting a single signature (List<T> list ->):</p> + * + * <code>public <T> T apply(T src, @ClosureParams(value=FromString.class, options="List<T>" Closure<T> cl)</code> + * + * <p>The next example describes a closure as accepting two signatures (List<T> list ->) and (T t ->):</p> + * + * <code>public <T> T apply(T src, @ClosureParams(value=FromString.class, options={"List<T>","T"} Closure<T> cl)</code> + * + * <p>It is advisable not to use this hint as a replacement for the various {@link FirstParam}, {@link SimpleType}, + * ... hints because it is actually much slower. Using this hint should therefore be limited + * to cases where it's not possible to express the signature using the existing hints.</p> + * + * @author Cédric Champeau + * @since 2.3.0 + */ +public class FromString extends ClosureSignatureHint { + + @Override + public List<ClassNode[]> getClosureSignatures(final MethodNode node, final SourceUnit sourceUnit, final CompilationUnit compilationUnit, final String[] options, final ASTNode usage) { + List<ClassNode[]> list = new ArrayList<ClassNode[]>(options.length); + for (String option : options) { + list.add(parseOption(option, sourceUnit, compilationUnit, node, usage)); + } + return list; + } + + /** + * Parses a string representing a type, that must be aligned with the current context. + * For example, <i>"List<T>"</i> must be converted into the appropriate ClassNode + * for which <i>T</i> matches the appropriate placeholder. + * + * + * @param option a string representing a type + * @param sourceUnit the source unit (of the file being compiled) + * @param compilationUnit the compilation unit (of the file being compiled) + * @param mn the method node + * @param usage + * @return a class node if it could be parsed and resolved, null otherwise + */ + private static ClassNode[] parseOption(final String option, final SourceUnit sourceUnit, final CompilationUnit compilationUnit, final MethodNode mn, final ASTNode usage) { + return GenericsUtils.parseClassNodesFromString(option, sourceUnit, compilationUnit, mn, usage); + } + +}
http://git-wip-us.apache.org/repos/asf/groovy/blob/10110145/src/main/groovy/groovy/transform/stc/IncorrectTypeHintException.java ---------------------------------------------------------------------- diff --git a/src/main/groovy/groovy/transform/stc/IncorrectTypeHintException.java b/src/main/groovy/groovy/transform/stc/IncorrectTypeHintException.java new file mode 100644 index 0000000..aed167a --- /dev/null +++ b/src/main/groovy/groovy/transform/stc/IncorrectTypeHintException.java @@ -0,0 +1,32 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one + * or more contributor license agreements. See the NOTICE file + * distributed with this work for additional information + * regarding copyright ownership. The ASF licenses this file + * to you 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 groovy.transform.stc; + +import org.codehaus.groovy.ast.MethodNode; +import org.codehaus.groovy.syntax.SyntaxException; + +public class IncorrectTypeHintException extends SyntaxException { + public IncorrectTypeHintException(final MethodNode mn, final Throwable e, int line, int column) { + super("Incorrect type hint in @ClosureParams in class "+mn.getDeclaringClass().getName()+" method "+mn.getTypeDescriptor()+" : "+e.getMessage(), e, line, column); + } + + public IncorrectTypeHintException(final MethodNode mn, final String msg, final int line, final int column) { + super("Incorrect type hint in @ClosureParams in class "+mn.getDeclaringClass().getName()+" method "+mn.getTypeDescriptor()+" : "+msg, line, column); + } +} http://git-wip-us.apache.org/repos/asf/groovy/blob/10110145/src/main/groovy/groovy/transform/stc/MapEntryOrKeyValue.java ---------------------------------------------------------------------- diff --git a/src/main/groovy/groovy/transform/stc/MapEntryOrKeyValue.java b/src/main/groovy/groovy/transform/stc/MapEntryOrKeyValue.java new file mode 100644 index 0000000..9316ef8 --- /dev/null +++ b/src/main/groovy/groovy/transform/stc/MapEntryOrKeyValue.java @@ -0,0 +1,119 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one + * or more contributor license agreements. See the NOTICE file + * distributed with this work for additional information + * regarding copyright ownership. The ASF licenses this file + * to you 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 groovy.transform.stc; + +import org.codehaus.groovy.ast.ASTNode; +import org.codehaus.groovy.ast.ClassHelper; +import org.codehaus.groovy.ast.ClassNode; +import org.codehaus.groovy.ast.GenericsType; +import org.codehaus.groovy.ast.MethodNode; +import org.codehaus.groovy.control.CompilationUnit; +import org.codehaus.groovy.control.SourceUnit; + +import java.util.Arrays; +import java.util.Collections; +import java.util.List; +import java.util.Map; + +/** + * <p>A special hint which handles a common use case in the Groovy methods that work on maps. In case of an + * iteration on a list of map entries, you often want the user to be able to work either on a {@link java.util.Map.Entry} map entry + * or on a key,value pair.</p> + * <p>The result is a closure which can have the following forms:</p> + * <ul> + * <li><code>{ key, value -> ...}</code> where key is the key of a map entry, and value the corresponding value</li> + * <li><code>{ entry -> ... }</code> where entry is a {@link java.util.Map.Entry} map entry</li> + * <li><code>{ ...}</code> where <i>it</i> is an implicit {@link java.util.Map.Entry} map entry</li> + * </ul> + * <p>This hint handles all those cases by picking the generics from the first argument of the method (by default).</p> + * <p>The options array is used to modify the behavior of this hint. Each string in the option array consists of + * a key=value pair.</p> + * <ul> + * <li><i>argNum=index</i> of the parameter representing the map (by default, 0)</li> + * <li><i>index=true or false</i>, by default false. If true, then an additional "int" parameter is added, + * for "withIndex" variants</li> + * </ul> + * <code>void doSomething(String str, Map<K,>V map, @ClosureParams(value=MapEntryOrKeyValue.class,options="argNum=1") Closure c) { ... }</code> + */ +public class MapEntryOrKeyValue extends ClosureSignatureHint { + private static final ClassNode MAPENTRY_TYPE = ClassHelper.make(Map.Entry.class); + + public List<ClassNode[]> getClosureSignatures(final MethodNode node, final SourceUnit sourceUnit, final CompilationUnit compilationUnit, final String[] options, final ASTNode usage) { + Options opt; + try { + opt = Options.parse(node, usage, options); + } catch (IncorrectTypeHintException e) { + sourceUnit.addError(e); + return Collections.emptyList(); + } + GenericsType[] genericsTypes = node.getParameters()[opt.parameterIndex].getOriginType().getGenericsTypes(); + if (genericsTypes==null) { + // would happen if you have a raw Map type for example + genericsTypes = new GenericsType[] { + new GenericsType(ClassHelper.OBJECT_TYPE), + new GenericsType(ClassHelper.OBJECT_TYPE) + }; + } + ClassNode[] firstSig; + ClassNode[] secondSig; + ClassNode mapEntry = MAPENTRY_TYPE.getPlainNodeReference(); + mapEntry.setGenericsTypes(genericsTypes); + if (opt.generateIndex) { + firstSig = new ClassNode[] {genericsTypes[0].getType(), genericsTypes[1].getType(), ClassHelper.int_TYPE}; + secondSig = new ClassNode[] {mapEntry, ClassHelper.int_TYPE}; + + } else { + firstSig = new ClassNode[] {genericsTypes[0].getType(), genericsTypes[1].getType()}; + secondSig = new ClassNode[] {mapEntry}; + } + return Arrays.asList(firstSig, secondSig); + } + + private static final class Options { + final int parameterIndex; + final boolean generateIndex; + + private Options(final int parameterIndex, final boolean generateIndex) { + this.parameterIndex = parameterIndex; + this.generateIndex = generateIndex; + } + + static Options parse(MethodNode mn, ASTNode source, String[] options) throws IncorrectTypeHintException { + int pIndex = 0; + boolean generateIndex = false; + for (String option : options) { + String[] keyValue = option.split("="); + if (keyValue.length==2) { + String key = keyValue[0]; + String value = keyValue[1]; + if ("argNum".equals(key)) { + pIndex = Integer.parseInt(value); + } else if ("index".equals(key)) { + generateIndex = Boolean.valueOf(value); + } else { + throw new IncorrectTypeHintException(mn, "Unrecognized option: "+key, source.getLineNumber(), source.getColumnNumber()); + } + } else { + throw new IncorrectTypeHintException(mn, "Incorrect option format. Should be argNum=<num> or index=<boolean> ", source.getLineNumber(), source.getColumnNumber()); + } + } + return new Options(pIndex, generateIndex); + } + } +} http://git-wip-us.apache.org/repos/asf/groovy/blob/10110145/src/main/groovy/groovy/transform/stc/PickAnyArgumentHint.java ---------------------------------------------------------------------- diff --git a/src/main/groovy/groovy/transform/stc/PickAnyArgumentHint.java b/src/main/groovy/groovy/transform/stc/PickAnyArgumentHint.java new file mode 100644 index 0000000..6a5463f --- /dev/null +++ b/src/main/groovy/groovy/transform/stc/PickAnyArgumentHint.java @@ -0,0 +1,75 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one + * or more contributor license agreements. See the NOTICE file + * distributed with this work for additional information + * regarding copyright ownership. The ASF licenses this file + * to you 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 groovy.transform.stc; + +import org.codehaus.groovy.ast.ASTNode; +import org.codehaus.groovy.ast.ClassNode; +import org.codehaus.groovy.ast.MethodNode; +import org.codehaus.groovy.control.CompilationUnit; +import org.codehaus.groovy.control.SourceUnit; + +/** + * <p>Base class for hints which use the type of a parameter of the annotated method as the signature. + * This can optionally use a generic type of the selected parameter as the hint. For example, imagine the following + * method:</p> + * <code>void foo(A firstArg, B secondArg, Closure c) {...}</code> + * <p>If the <i>c</i> closure should be <code>{ B it -> ...}</code>, then we can see that the parameter type + * should be picked from the second parameter of the foo method, which is what {@link groovy.transform.stc.PickAnyArgumentHint} + * lets you do.</p> + * <p>Alternatively, the method may look like this:</p> + * <code>void <T> foo(A<T> firstArg, B secondArg, Closure c) {...}</code> + * <p>in which case if you want to express the fact that <i>c</i> should accept a <T> then you can use the + * {@link #genericTypeIndex} value.</p> + * <p></p> + * <p>This class is extended by several hint providers that make it easier to use as annotation values.</p> + * + * @author Cédric Champeau + * @since 2.3.0 + */ +public class PickAnyArgumentHint extends SingleSignatureClosureHint { + private final int parameterIndex; + private final int genericTypeIndex; + + /** + * Creates the an argument picker which extracts the type of the first parameter. + */ + public PickAnyArgumentHint() { + this(0,-1); + } + + /** + * Creates a picker which will extract the parameterIndex-th parameter type, or its + * genericTypeIndex-th generic type genericTypeIndex is >=0. + * @param parameterIndex the index of the parameter from which to extract the type + * @param genericTypeIndex if >=0, then returns the corresponding generic type instead of the parameter type. + */ + public PickAnyArgumentHint(final int parameterIndex, final int genericTypeIndex) { + this.parameterIndex = parameterIndex; + this.genericTypeIndex = genericTypeIndex; + } + + @Override + public ClassNode[] getParameterTypes(final MethodNode node, final String[] options, final SourceUnit sourceUnit, final CompilationUnit unit, final ASTNode usage) { + ClassNode type = node.getParameters()[parameterIndex].getOriginType(); + if (genericTypeIndex>=0) { + type = pickGenericType(type, genericTypeIndex); + } + return new ClassNode[]{type}; + } +} http://git-wip-us.apache.org/repos/asf/groovy/blob/10110145/src/main/groovy/groovy/transform/stc/PickFirstResolver.java ---------------------------------------------------------------------- diff --git a/src/main/groovy/groovy/transform/stc/PickFirstResolver.java b/src/main/groovy/groovy/transform/stc/PickFirstResolver.java new file mode 100644 index 0000000..0bebb3e --- /dev/null +++ b/src/main/groovy/groovy/transform/stc/PickFirstResolver.java @@ -0,0 +1,45 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one + * or more contributor license agreements. See the NOTICE file + * distributed with this work for additional information + * regarding copyright ownership. The ASF licenses this file + * to you 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 groovy.transform.stc; + +import org.codehaus.groovy.ast.ClassNode; +import org.codehaus.groovy.ast.MethodNode; +import org.codehaus.groovy.ast.expr.ClosureExpression; +import org.codehaus.groovy.ast.expr.Expression; +import org.codehaus.groovy.control.CompilationUnit; +import org.codehaus.groovy.control.SourceUnit; + +import java.util.Collections; +import java.util.List; + +/** + * Returns the first of several candidates found. + * This is useful if several types should be supported but only the first + * should be the default/inferred type. Other options in the list are + * obtained through explicitly typing the parameter(s). + * + * @since 2.5.0 + */ +public class PickFirstResolver extends ClosureSignatureConflictResolver { + @Override + public List<ClassNode[]> resolve(List<ClassNode[]> candidates, ClassNode receiver, Expression arguments, ClosureExpression closure, + MethodNode methodNode, SourceUnit sourceUnit, CompilationUnit compilationUnit, String[] options) { + return Collections.singletonList(candidates.get(0)); + } +} http://git-wip-us.apache.org/repos/asf/groovy/blob/10110145/src/main/groovy/groovy/transform/stc/SecondParam.java ---------------------------------------------------------------------- diff --git a/src/main/groovy/groovy/transform/stc/SecondParam.java b/src/main/groovy/groovy/transform/stc/SecondParam.java new file mode 100644 index 0000000..c077750 --- /dev/null +++ b/src/main/groovy/groovy/transform/stc/SecondParam.java @@ -0,0 +1,93 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one + * or more contributor license agreements. See the NOTICE file + * distributed with this work for additional information + * regarding copyright ownership. The ASF licenses this file + * to you 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 groovy.transform.stc; + +import org.codehaus.groovy.ast.ASTNode; +import org.codehaus.groovy.ast.ClassNode; +import org.codehaus.groovy.ast.MethodNode; +import org.codehaus.groovy.control.CompilationUnit; +import org.codehaus.groovy.control.SourceUnit; + +/** + * <p>A hint used to instruct the type checker to pick the second parameter type. For example:</p> + * <code>public <T,U> def doWith(T first, U second, @ClosureParams(SecondParam.class) Closure c) { c.call(src); }</code> + * + * <p>This class has several inner classes that also helps picking generic argument types instead of the parameter type.</p> + * + * @author Cédric Champeau + * @since 2.3.0 + */ +public class SecondParam extends PickAnyArgumentHint { + public SecondParam() { + super(1,-1); + } + + /** + * <p>A hint used to instruct the type checker to pick the first generic type of the second parameter type. For example:</p> + * <code>void <T> doWithElements(String base, List<T> src, @ClosureParams(SecondParam.FirstGenericType.class) Closure c) { ... } }</code> + * + * @author Cédric Champeau + * @since 2.3.0 + */ + public static class FirstGenericType extends PickAnyArgumentHint { + public FirstGenericType() { + super(1,0); + } + } + + /** + * <p>A hint used to instruct the type checker to pick the second generic type of the second parameter type. For example:</p> + * <code>void <T,U> doWithElements(String base, Tuple<T,U> src, @ClosureParams(SecondParam.SecondGenericType.class) Closure c) { ... }</code> + * + * @author Cédric Champeau + * @since 2.3.0 + */ + public static class SecondGenericType extends PickAnyArgumentHint { + public SecondGenericType() { + super(1,1); + } + } + + /** + * <p>A hint used to instruct the type checker to pick the second generic type of the second parameter type. For example:</p> + * <code>void <T,U,V> doWithElements(String base, Triple<T,U,V> src, @ClosureParams(SecondParam.ThirdGenericType.class) Closure c) { ... }</code> + * + * @author Cédric Champeau + * @since 2.3.0 + */ + public static class ThirdGenericType extends PickAnyArgumentHint { + public ThirdGenericType() { + super(1,2); + } + } + + /** + * <p>A hint used to instruct the type checker to pick the type of the component of the second parameter type, which is therefore + * expected to be an array, like in this example:</p> + * <code>void <T> doWithArray(String first, T[] array, @ClosureParams(FirstParam.Component.class) Closure c) { ... }</code> + */ + public static class Component extends SecondParam { + @Override + public ClassNode[] getParameterTypes(final MethodNode node, final String[] options, final SourceUnit sourceUnit, final CompilationUnit unit, final ASTNode usage) { + final ClassNode[] parameterTypes = super.getParameterTypes(node, options, sourceUnit, unit, usage); + parameterTypes[0] = parameterTypes[0].getComponentType(); + return parameterTypes; + } + } +} http://git-wip-us.apache.org/repos/asf/groovy/blob/10110145/src/main/groovy/groovy/transform/stc/SimpleType.java ---------------------------------------------------------------------- diff --git a/src/main/groovy/groovy/transform/stc/SimpleType.java b/src/main/groovy/groovy/transform/stc/SimpleType.java new file mode 100644 index 0000000..8866e3b --- /dev/null +++ b/src/main/groovy/groovy/transform/stc/SimpleType.java @@ -0,0 +1,36 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one + * or more contributor license agreements. See the NOTICE file + * distributed with this work for additional information + * regarding copyright ownership. The ASF licenses this file + * to you 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 groovy.transform.stc; + +import org.codehaus.groovy.ast.ASTNode; +import org.codehaus.groovy.ast.ClassNode; +import org.codehaus.groovy.ast.MethodNode; +import org.codehaus.groovy.control.CompilationUnit; +import org.codehaus.groovy.control.SourceUnit; + +public class SimpleType extends SingleSignatureClosureHint { + @Override + public ClassNode[] getParameterTypes(final MethodNode node, final String[] options, final SourceUnit sourceUnit, final CompilationUnit unit, final ASTNode usage) { + ClassNode[] result = new ClassNode[options.length]; + for (int i = 0; i < result.length; i++) { + result[i] = findClassNode(sourceUnit, unit, options[i]); + } + return result; + } + } http://git-wip-us.apache.org/repos/asf/groovy/blob/10110145/src/main/groovy/groovy/transform/stc/SingleSignatureClosureHint.java ---------------------------------------------------------------------- diff --git a/src/main/groovy/groovy/transform/stc/SingleSignatureClosureHint.java b/src/main/groovy/groovy/transform/stc/SingleSignatureClosureHint.java new file mode 100644 index 0000000..3db66ff --- /dev/null +++ b/src/main/groovy/groovy/transform/stc/SingleSignatureClosureHint.java @@ -0,0 +1,44 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one + * or more contributor license agreements. See the NOTICE file + * distributed with this work for additional information + * regarding copyright ownership. The ASF licenses this file + * to you 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 groovy.transform.stc; + +import org.codehaus.groovy.ast.ASTNode; +import org.codehaus.groovy.ast.ClassNode; +import org.codehaus.groovy.ast.MethodNode; +import org.codehaus.groovy.control.CompilationUnit; +import org.codehaus.groovy.control.SourceUnit; + +import java.util.Collections; +import java.util.List; + +/** + * A simplified version of a {@link groovy.transform.stc.ClosureSignatureHint} which is suitable + * for monomorphic closures, that is to say closures which only respond to a single signature. + * + * @author Cédric Champeau + * @since 2.3.0 + */ +public abstract class SingleSignatureClosureHint extends ClosureSignatureHint { + + public abstract ClassNode[] getParameterTypes(final MethodNode node, final String[] options, final SourceUnit sourceUnit, final CompilationUnit unit, final ASTNode usage); + + public List<ClassNode[]> getClosureSignatures(final MethodNode node, final SourceUnit sourceUnit, final CompilationUnit compilationUnit, final String[] options, final ASTNode usage) { + return Collections.singletonList(getParameterTypes(node, options, sourceUnit, compilationUnit, usage)); + } +} http://git-wip-us.apache.org/repos/asf/groovy/blob/10110145/src/main/groovy/groovy/transform/stc/ThirdParam.java ---------------------------------------------------------------------- diff --git a/src/main/groovy/groovy/transform/stc/ThirdParam.java b/src/main/groovy/groovy/transform/stc/ThirdParam.java new file mode 100644 index 0000000..c493e36 --- /dev/null +++ b/src/main/groovy/groovy/transform/stc/ThirdParam.java @@ -0,0 +1,94 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one + * or more contributor license agreements. See the NOTICE file + * distributed with this work for additional information + * regarding copyright ownership. The ASF licenses this file + * to you 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 groovy.transform.stc; + +import org.codehaus.groovy.ast.ASTNode; +import org.codehaus.groovy.ast.ClassNode; +import org.codehaus.groovy.ast.MethodNode; +import org.codehaus.groovy.control.CompilationUnit; +import org.codehaus.groovy.control.SourceUnit; + +/** + * <p>A hint used to instruct the type checker to pick the third parameter type. For example:</p> + * <code>public <T,U,V> def doWith(T first, U second, V third, @ClosureParams(ThirdParam.class) Closure c) { ... }</code> + * + * <p>This class has several inner classes that also helps picking generic argument types instead of the parameter type.</p> + * + * @author Cédric Champeau + * @since 2.3.0 + */ +public class ThirdParam extends PickAnyArgumentHint { + public ThirdParam() { + super(2,-1); + } + + /** + * <p>A hint used to instruct the type checker to pick the first generic type of the third parameter type. For example:</p> + * <code>void <T> doWithElements(String first, Integer second, List<T> third, @ClosureParams(SecondParam.FirstGenericType.class) Closure c) { ... } }</code> + * + * @author Cédric Champeau + * @since 2.3.0 + */ + public static class FirstGenericType extends PickAnyArgumentHint { + public FirstGenericType() { + super(2,0); + } + } + + + /** + * <p>A hint used to instruct the type checker to pick the second generic type of the third parameter type. For example:</p> + * <code>void <T,U> doWithElements(String first, Integer second, Tuple<T,U> third, @ClosureParams(SecondParam.SecondGenericType.class) Closure c) { ... }</code> + * + * @author Cédric Champeau + * @since 2.3.0 + */ + public static class SecondGenericType extends PickAnyArgumentHint { + public SecondGenericType() { + super(2,1); + } + } + + /** + * <p>A hint used to instruct the type checker to pick the second generic type of the third parameter type. For example:</p> + * <code>void <T,U,V> doWithElements(String first, Integer second, Triple<T,U,V> src, @ClosureParams(SecondParam.ThirdGenericType.class) Closure c) { ... }</code> + * + * @author Cédric Champeau + * @since 2.3.0 + */ + public static class ThirdGenericType extends PickAnyArgumentHint { + public ThirdGenericType() { + super(2,2); + } + } + + /** + * <p>A hint used to instruct the type checker to pick the type of the component of the third parameter type, which is therefore + * expected to be an array, like in this example:</p> + * <code>void <T> doWithArray(String first, int second, T[] third, @ClosureParams(FirstParam.Component.class) Closure c) { ... }</code> + */ + public static class Component extends ThirdParam { + @Override + public ClassNode[] getParameterTypes(final MethodNode node, final String[] options, final SourceUnit sourceUnit, final CompilationUnit unit, final ASTNode usage) { + final ClassNode[] parameterTypes = super.getParameterTypes(node, options, sourceUnit, unit, usage); + parameterTypes[0] = parameterTypes[0].getComponentType(); + return parameterTypes; + } + } +} http://git-wip-us.apache.org/repos/asf/groovy/blob/10110145/src/main/groovy/groovy/ui/GroovyMain.java ---------------------------------------------------------------------- diff --git a/src/main/groovy/groovy/ui/GroovyMain.java b/src/main/groovy/groovy/ui/GroovyMain.java new file mode 100644 index 0000000..a4d5986 --- /dev/null +++ b/src/main/groovy/groovy/ui/GroovyMain.java @@ -0,0 +1,597 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one + * or more contributor license agreements. See the NOTICE file + * distributed with this work for additional information + * regarding copyright ownership. The ASF licenses this file + * to you 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 groovy.ui; + +import groovy.lang.Binding; +import groovy.lang.GroovyCodeSource; +import groovy.lang.GroovyRuntimeException; +import groovy.lang.GroovyShell; +import groovy.lang.GroovySystem; +import groovy.lang.MissingMethodException; +import groovy.lang.Script; +import org.apache.commons.cli.CommandLine; +import org.apache.commons.cli.CommandLineParser; +import org.apache.commons.cli.DefaultParser; +import org.apache.commons.cli.HelpFormatter; +import org.apache.commons.cli.Options; +import org.apache.commons.cli.ParseException; +import org.codehaus.groovy.control.CompilationFailedException; +import org.codehaus.groovy.control.CompilerConfiguration; +import org.codehaus.groovy.control.customizers.ImportCustomizer; +import org.codehaus.groovy.runtime.InvokerHelper; +import org.codehaus.groovy.runtime.InvokerInvocationException; +import org.codehaus.groovy.runtime.ResourceGroovyMethods; +import org.codehaus.groovy.runtime.StackTraceUtils; +import org.codehaus.groovy.runtime.StringGroovyMethods; + +import java.io.BufferedReader; +import java.io.File; +import java.io.FileNotFoundException; +import java.io.FileReader; +import java.io.FileWriter; +import java.io.IOException; +import java.io.InputStreamReader; +import java.io.PrintStream; +import java.io.PrintWriter; +import java.math.BigInteger; +import java.net.URI; +import java.net.URISyntaxException; +import java.net.URL; +import java.security.AccessController; +import java.security.PrivilegedAction; +import java.util.ArrayList; +import java.util.Enumeration; +import java.util.Iterator; +import java.util.List; +import java.util.Properties; +import java.util.regex.Pattern; + +import static org.apache.commons.cli.Option.builder; + +/** + * A Command line to execute groovy. + */ +public class GroovyMain { + + // arguments to the script + private List args; + + // is this a file on disk + private boolean isScriptFile; + + // filename or content of script + private String script; + + // process args as input files + private boolean processFiles; + + // edit input files in place + private boolean editFiles; + + // automatically output the result of each script + private boolean autoOutput; + + // automatically split each line using the splitpattern + private boolean autoSplit; + + // The pattern used to split the current line + private String splitPattern = " "; + + // process sockets + private boolean processSockets; + + // port to listen on when processing sockets + private int port; + + // backup input files with extension + private String backupExtension; + + // do you want full stack traces in script exceptions? + private boolean debug = false; + + // Compiler configuration, used to set the encodings of the scripts/classes + private CompilerConfiguration conf = new CompilerConfiguration(System.getProperties()); + + /** + * Main CLI interface. + * + * @param args all command line args. + */ + public static void main(String args[]) { + processArgs(args, System.out); + } + + // package-level visibility for testing purposes (just usage/errors at this stage) + // TODO: should we have an 'err' printstream too for ParseException? + static void processArgs(String[] args, final PrintStream out) { + Options options = buildOptions(); + + try { + CommandLine cmd = parseCommandLine(options, args); + + if (cmd.hasOption('h')) { + printHelp(out, options); + } else if (cmd.hasOption('v')) { + String version = GroovySystem.getVersion(); + out.println("Groovy Version: " + version + " JVM: " + System.getProperty("java.version") + + " Vendor: " + System.getProperty("java.vm.vendor") + " OS: " + System.getProperty("os.name")); + } else { + // If we fail, then exit with an error so scripting frameworks can catch it + // TODO: pass printstream(s) down through process + if (!process(cmd)) { + System.exit(1); + } + } + } catch (ParseException pe) { + out.println("error: " + pe.getMessage()); + printHelp(out, options); + } catch (IOException ioe) { + out.println("error: " + ioe.getMessage()); + } + } + + private static void printHelp(PrintStream out, Options options) { + HelpFormatter formatter = new HelpFormatter(); + PrintWriter pw = new PrintWriter(out); + + formatter.printHelp( + pw, + 80, + "groovy [options] [filename] [args]", + "The Groovy command line processor.\nOptions:", + options, + 2, + 4, + null, // footer + false); + + pw.flush(); + } + + /** + * Parse the command line. + * + * @param options the options parser. + * @param args the command line args. + * @return parsed command line. + * @throws ParseException if there was a problem. + */ + private static CommandLine parseCommandLine(Options options, String[] args) throws ParseException { + CommandLineParser parser = new DefaultParser(); + return parser.parse(options, args, true); + } + + /** + * Build the options parser. + * + * @return an options parser. + */ + private static Options buildOptions() { + return new Options() + .addOption(builder("classpath").hasArg().argName("path").desc("Specify where to find the class files - must be first argument").build()) + .addOption(builder("cp").longOpt("classpath").hasArg().argName("path").desc("Aliases for '-classpath'").build()) + .addOption(builder("D").longOpt("define").desc("Define a system property").numberOfArgs(2).valueSeparator().argName("name=value").build()) + .addOption( + builder().longOpt("disableopt") + .desc("Disables one or all optimization elements; " + + "optlist can be a comma separated list with the elements: " + + "all (disables all optimizations), " + + "int (disable any int based optimizations)") + .hasArg().argName("optlist").build()) + .addOption(builder("h").hasArg(false).desc("Usage information").longOpt("help").build()) + .addOption(builder("d").hasArg(false).desc("Debug mode will print out full stack traces").longOpt("debug").build()) + .addOption(builder("v").hasArg(false).desc("Display the Groovy and JVM versions").longOpt("version").build()) + .addOption(builder("c").argName("charset").hasArg().desc("Specify the encoding of the files").longOpt("encoding").build()) + .addOption(builder("e").argName("script").hasArg().desc("Specify a command line script").build()) + .addOption(builder("i").argName("extension").optionalArg(true).desc("Modify files in place; create backup if extension is given (e.g. \'.bak\')").build()) + .addOption(builder("n").hasArg(false).desc("Process files line by line using implicit 'line' variable").build()) + .addOption(builder("p").hasArg(false).desc("Process files line by line and print result (see also -n)").build()) + .addOption(builder("pa").hasArg(false).desc("Generate metadata for reflection on method parameter names (jdk8+ only)").longOpt("parameters").build()) + .addOption(builder("l").argName("port").optionalArg(true).desc("Listen on a port and process inbound lines (default: 1960)").build()) + .addOption(builder("a").argName("splitPattern").optionalArg(true).desc("Split lines using splitPattern (default '\\s') using implicit 'split' variable").longOpt("autosplit").build()) + .addOption(builder().longOpt("indy").desc("Enables compilation using invokedynamic").build()) + .addOption(builder().longOpt("configscript").hasArg().desc("A script for tweaking the configuration options").build()) + .addOption(builder("b").longOpt("basescript").hasArg().argName("class").desc("Base class name for scripts (must derive from Script)").build()); + } + + /** + * Process the users request. + * + * @param line the parsed command line. + * @throws ParseException if invalid options are chosen + */ + private static boolean process(CommandLine line) throws ParseException, IOException { + List args = line.getArgList(); + + if (line.hasOption('D')) { + Properties optionProperties = line.getOptionProperties("D"); + Enumeration<String> propertyNames = (Enumeration<String>) optionProperties.propertyNames(); + while (propertyNames.hasMoreElements()) { + String nextName = propertyNames.nextElement(); + System.setProperty(nextName, optionProperties.getProperty(nextName)); + } + } + + GroovyMain main = new GroovyMain(); + + // add the ability to parse scripts with a specified encoding + main.conf.setSourceEncoding(line.getOptionValue('c',main.conf.getSourceEncoding())); + + main.isScriptFile = !line.hasOption('e'); + main.debug = line.hasOption('d'); + main.conf.setDebug(main.debug); + main.conf.setParameters(line.hasOption("pa")); + main.processFiles = line.hasOption('p') || line.hasOption('n'); + main.autoOutput = line.hasOption('p'); + main.editFiles = line.hasOption('i'); + if (main.editFiles) { + main.backupExtension = line.getOptionValue('i'); + } + main.autoSplit = line.hasOption('a'); + String sp = line.getOptionValue('a'); + if (sp != null) + main.splitPattern = sp; + + if (main.isScriptFile) { + if (args.isEmpty()) + throw new ParseException("neither -e or filename provided"); + + main.script = (String) args.remove(0); + if (main.script.endsWith(".java")) + throw new ParseException("error: cannot compile file with .java extension: " + main.script); + } else { + main.script = line.getOptionValue('e'); + } + + main.processSockets = line.hasOption('l'); + if (main.processSockets) { + String p = line.getOptionValue('l', "1960"); // default port to listen to + main.port = Integer.parseInt(p); + } + + // we use "," as default, because then split will create + // an empty array if no option is set + String disabled = line.getOptionValue("disableopt", ","); + String[] deopts = disabled.split(","); + for (String deopt_i : deopts) { + main.conf.getOptimizationOptions().put(deopt_i,false); + } + + if (line.hasOption("indy")) { + CompilerConfiguration.DEFAULT.getOptimizationOptions().put("indy", true); + main.conf.getOptimizationOptions().put("indy", true); + } + + if (line.hasOption("basescript")) { + main.conf.setScriptBaseClass(line.getOptionValue("basescript")); + } + + String configScripts = System.getProperty("groovy.starter.configscripts", null); + if (line.hasOption("configscript") || (configScripts != null && !configScripts.isEmpty())) { + List<String> scripts = new ArrayList<String>(); + if (line.hasOption("configscript")) { + scripts.add(line.getOptionValue("configscript")); + } + if (configScripts != null) { + scripts.addAll(StringGroovyMethods.tokenize((CharSequence) configScripts, ',')); + } + processConfigScripts(scripts, main.conf); + } + + main.args = args; + return main.run(); + } + + public static void processConfigScripts(List<String> scripts, CompilerConfiguration conf) throws IOException { + if (scripts.isEmpty()) return; + Binding binding = new Binding(); + binding.setVariable("configuration", conf); + CompilerConfiguration configuratorConfig = new CompilerConfiguration(); + ImportCustomizer customizer = new ImportCustomizer(); + customizer.addStaticStars("org.codehaus.groovy.control.customizers.builder.CompilerCustomizationBuilder"); + configuratorConfig.addCompilationCustomizers(customizer); + GroovyShell shell = new GroovyShell(binding, configuratorConfig); + for (String script : scripts) { + shell.evaluate(new File(script)); + } + } + + + /** + * Run the script. + */ + private boolean run() { + try { + if (processSockets) { + processSockets(); + } else if (processFiles) { + processFiles(); + } else { + processOnce(); + } + return true; + } catch (CompilationFailedException e) { + System.err.println(e); + return false; + } catch (Throwable e) { + if (e instanceof InvokerInvocationException) { + InvokerInvocationException iie = (InvokerInvocationException) e; + e = iie.getCause(); + } + System.err.println("Caught: " + e); + if (!debug) { + StackTraceUtils.deepSanitize(e); + } + e.printStackTrace(); + return false; + } + } + + /** + * Process Sockets. + */ + private void processSockets() throws CompilationFailedException, IOException, URISyntaxException { + GroovyShell groovy = new GroovyShell(conf); + new GroovySocketServer(groovy, getScriptSource(isScriptFile, script), autoOutput, port); + } + + /** + * Get the text of the Groovy script at the given location. + * If the location is a file path and it does not exist as given, + * then {@link GroovyMain#huntForTheScriptFile(String)} is called to try + * with some Groovy extensions appended. + * + * This method is not used to process scripts and is retained for backward + * compatibility. If you want to modify how GroovyMain processes scripts + * then use {@link GroovyMain#getScriptSource(boolean, String)}. + * + * @param uriOrFilename + * @return the text content at the location + * @throws IOException + * @deprecated + */ + @Deprecated + public String getText(String uriOrFilename) throws IOException { + if (URI_PATTERN.matcher(uriOrFilename).matches()) { + try { + return ResourceGroovyMethods.getText(new URL(uriOrFilename)); + } catch (Exception e) { + throw new GroovyRuntimeException("Unable to get script from URL: ", e); + } + } + return ResourceGroovyMethods.getText(huntForTheScriptFile(uriOrFilename)); + } + + /** + * Get a new GroovyCodeSource for a script which may be given as a location + * (isScript is true) or as text (isScript is false). + * + * @param isScriptFile indicates whether the script parameter is a location or content + * @param script the location or context of the script + * @return a new GroovyCodeSource for the given script + * @throws IOException + * @throws URISyntaxException + * @since 2.3.0 + */ + protected GroovyCodeSource getScriptSource(boolean isScriptFile, String script) throws IOException, URISyntaxException { + //check the script is currently valid before starting a server against the script + if (isScriptFile) { + // search for the file and if it exists don't try to use URIs ... + File scriptFile = huntForTheScriptFile(script); + if (!scriptFile.exists() && URI_PATTERN.matcher(script).matches()) { + return new GroovyCodeSource(new URI(script)); + } + return new GroovyCodeSource( scriptFile ); + } + return new GroovyCodeSource(script, "script_from_command_line", GroovyShell.DEFAULT_CODE_BASE); + } + + // RFC2396 + // scheme = alpha *( alpha | digit | "+" | "-" | "." ) + // match URIs but not Windows filenames, e.g.: http://cnn.com but not C:\xxx\file.ext + private static final Pattern URI_PATTERN = Pattern.compile("\\p{Alpha}[-+.\\p{Alnum}]*:[^\\\\]*"); + + /** + * Search for the script file, doesn't bother if it is named precisely. + * + * Tries in this order: + * - actual supplied name + * - name.groovy + * - name.gvy + * - name.gy + * - name.gsh + * + * @since 2.3.0 + */ + public static File searchForGroovyScriptFile(String input) { + String scriptFileName = input.trim(); + File scriptFile = new File(scriptFileName); + // TODO: Shouldn't these extensions be kept elsewhere? What about CompilerConfiguration? + // This method probably shouldn't be in GroovyMain either. + String[] standardExtensions = {".groovy",".gvy",".gy",".gsh"}; + int i = 0; + while (i < standardExtensions.length && !scriptFile.exists()) { + scriptFile = new File(scriptFileName + standardExtensions[i]); + i++; + } + // if we still haven't found the file, point back to the originally specified filename + if (!scriptFile.exists()) { + scriptFile = new File(scriptFileName); + } + return scriptFile; + } + + /** + * Hunt for the script file by calling searchForGroovyScriptFile(String). + * + * @see GroovyMain#searchForGroovyScriptFile(String) + */ + public File huntForTheScriptFile(String input) { + return GroovyMain.searchForGroovyScriptFile(input); + } + + // GROOVY-6771 + private static void setupContextClassLoader(GroovyShell shell) { + final Thread current = Thread.currentThread(); + class DoSetContext implements PrivilegedAction { + ClassLoader classLoader; + + public DoSetContext(ClassLoader loader) { + classLoader = loader; + } + + public Object run() { + current.setContextClassLoader(classLoader); + return null; + } + } + + AccessController.doPrivileged(new DoSetContext(shell.getClassLoader())); + } + + /** + * Process the input files. + */ + private void processFiles() throws CompilationFailedException, IOException, URISyntaxException { + GroovyShell groovy = new GroovyShell(conf); + setupContextClassLoader(groovy); + + Script s = groovy.parse(getScriptSource(isScriptFile, script)); + + if (args.isEmpty()) { + BufferedReader reader = new BufferedReader(new InputStreamReader(System.in)); + PrintWriter writer = new PrintWriter(System.out); + + try { + processReader(s, reader, writer); + } finally { + reader.close(); + writer.close(); + } + + } else { + Iterator i = args.iterator(); + while (i.hasNext()) { + String filename = (String) i.next(); + //TODO: These are the arguments for -p and -i. Why are we searching using Groovy script extensions? + // Where is this documented? + File file = huntForTheScriptFile(filename); + processFile(s, file); + } + } + } + + /** + * Process a single input file. + * + * @param s the script to execute. + * @param file the input file. + */ + private void processFile(Script s, File file) throws IOException { + if (!file.exists()) + throw new FileNotFoundException(file.getName()); + + if (!editFiles) { + BufferedReader reader = new BufferedReader(new FileReader(file)); + try { + PrintWriter writer = new PrintWriter(System.out); + processReader(s, reader, writer); + writer.flush(); + } finally { + reader.close(); + } + } else { + File backup; + if (backupExtension == null) { + backup = File.createTempFile("groovy_", ".tmp"); + backup.deleteOnExit(); + } else { + backup = new File(file.getPath() + backupExtension); + } + backup.delete(); + if (!file.renameTo(backup)) + throw new IOException("unable to rename " + file + " to " + backup); + + BufferedReader reader = new BufferedReader(new FileReader(backup)); + try { + PrintWriter writer = new PrintWriter(new FileWriter(file)); + try { + processReader(s, reader, writer); + } finally { + writer.close(); + } + } finally { + reader.close(); + } + } + } + + /** + * Process a script against a single input file. + * + * @param s script to execute. + * @param reader input file. + * @param pw output sink. + */ + private void processReader(Script s, BufferedReader reader, PrintWriter pw) throws IOException { + String line; + String lineCountName = "count"; + s.setProperty(lineCountName, BigInteger.ZERO); + String autoSplitName = "split"; + s.setProperty("out", pw); + + try { + InvokerHelper.invokeMethod(s, "begin", null); + } catch (MissingMethodException mme) { + // ignore the missing method exception + // as it means no begin() method is present + } + + while ((line = reader.readLine()) != null) { + s.setProperty("line", line); + s.setProperty(lineCountName, ((BigInteger)s.getProperty(lineCountName)).add(BigInteger.ONE)); + + if(autoSplit) { + s.setProperty(autoSplitName, line.split(splitPattern)); + } + + Object o = s.run(); + + if (autoOutput && o != null) { + pw.println(o); + } + } + + try { + InvokerHelper.invokeMethod(s, "end", null); + } catch (MissingMethodException mme) { + // ignore the missing method exception + // as it means no end() method is present + } + } + + /** + * Process the standard, single script with args. + */ + private void processOnce() throws CompilationFailedException, IOException, URISyntaxException { + GroovyShell groovy = new GroovyShell(conf); + setupContextClassLoader(groovy); + groovy.run(getScriptSource(isScriptFile, script), args); + } +} http://git-wip-us.apache.org/repos/asf/groovy/blob/10110145/src/main/groovy/groovy/ui/GroovySocketServer.java ---------------------------------------------------------------------- diff --git a/src/main/groovy/groovy/ui/GroovySocketServer.java b/src/main/groovy/groovy/ui/GroovySocketServer.java new file mode 100644 index 0000000..b0d27c5 --- /dev/null +++ b/src/main/groovy/groovy/ui/GroovySocketServer.java @@ -0,0 +1,226 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one + * or more contributor license agreements. See the NOTICE file + * distributed with this work for additional information + * regarding copyright ownership. The ASF licenses this file + * to you 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 groovy.ui; + +import groovy.lang.GroovyCodeSource; +import groovy.lang.GroovyRuntimeException; +import groovy.lang.GroovyShell; +import groovy.lang.Script; + +import java.io.BufferedReader; +import java.io.IOException; +import java.io.InputStreamReader; +import java.io.PrintWriter; +import java.net.InetAddress; +import java.net.ServerSocket; +import java.net.Socket; +import java.net.URI; +import java.net.URISyntaxException; +import java.net.URL; +import java.util.regex.Pattern; + +/** + * Simple server that executes supplied script against a socket. + * <p> + * Typically this is used from the groovy command line agent but it can be + * invoked programmatically. To run this program from the command line please + * refer to the command line documentation at + * <a href="http://docs.groovy-lang.org/docs/latest/html/documentation/#_running_groovy_from_the_commandline"> + * Running Groovy from the commandline</a>. + * <p> + * Here is an example of how to use this class to open a listening socket on the server, + * listen for incoming data, and then echo the data back to the client in reverse order: + * <pre> + * new GroovySocketServer( + * new GroovyShell(), // evaluator + * false, // is not a file + * "println line.reverse()", // script to evaluate + * true, // return result to client + * 1960) //port + * </pre> + * There are several variables in the script binding: + * <ul> + * <li>line - The data from the socket</li> + * <li>out - The output PrintWriter, should you need it for some reason.</li> + * <li>socket - The socket, should you need it for some reason.</li> + * </ul> + * + * @author Jeremy Rayner + */ +public class GroovySocketServer implements Runnable { + private URL url; + private final GroovyShell groovy; + private final GroovyCodeSource source; + private final boolean autoOutput; + private static int counter; + + /** + * This creates and starts the socket server on a new Thread. There is no need to call run or spawn + * a new thread yourself. + * @param groovy + * The GroovyShell object that evaluates the incoming text. If you need additional classes in the + * classloader then configure that through this object. + * @param isScriptFile + * Whether the incoming socket data String will be a script or a file path. + * @param scriptFilenameOrText + * This will be a groovy script or a file location depending on the argument isScriptFile. + * @param autoOutput + * whether output should be automatically echoed back to the client + * @param port + * the port to listen on + * + */ + public GroovySocketServer(GroovyShell groovy, boolean isScriptFile, String scriptFilenameOrText, boolean autoOutput, int port) { + this(groovy, getCodeSource(isScriptFile, scriptFilenameOrText), autoOutput, port); + } + + private static GroovyCodeSource getCodeSource(boolean scriptFile, String scriptFilenameOrText) { + if (scriptFile) { + try { + if (URI_PATTERN.matcher(scriptFilenameOrText).matches()) { + return new GroovyCodeSource(new URI(scriptFilenameOrText)); + } else { + return new GroovyCodeSource(GroovyMain.searchForGroovyScriptFile(scriptFilenameOrText)); + } + } catch (IOException e) { + throw new GroovyRuntimeException("Unable to get script from: " + scriptFilenameOrText, e); + } catch (URISyntaxException e) { + throw new GroovyRuntimeException("Unable to get script from URI: " + scriptFilenameOrText, e); + } + } else { + // We could jump through some hoops to have GroovyShell make our script name, but that seems unwarranted. + // If we *did* jump through that hoop then we should probably change the run loop to not recompile + // the script on every iteration since the script text can't change (the reason for the recompilation). + return new GroovyCodeSource(scriptFilenameOrText, generateScriptName(), GroovyShell.DEFAULT_CODE_BASE); + } + } + + private static synchronized String generateScriptName() { + return "ServerSocketScript" + (++counter) + ".groovy"; + } + + + // RFC2396 + // scheme = alpha *( alpha | digit | "+" | "-" | "." ) + private static final Pattern URI_PATTERN = Pattern.compile("\\p{Alpha}[-+.\\p{Alnum}]*:.*"); + + /** + * This creates and starts the socket server on a new Thread. There is no need to call run or spawn + * a new thread yourself. + * @param groovy + * The GroovyShell object that evaluates the incoming text. If you need additional classes in the + * classloader then configure that through this object. + * @param source + * GroovyCodeSource for the Groovy script + * @param autoOutput + * whether output should be automatically echoed back to the client + * @param port + * the port to listen on + * @since 2.3.0 + */ + public GroovySocketServer(GroovyShell groovy, GroovyCodeSource source, boolean autoOutput, int port) { + this.groovy = groovy; + this.source = source; + this.autoOutput = autoOutput; + try { + url = new URL("http", InetAddress.getLocalHost().getHostAddress(), port, "/"); + System.out.println("groovy is listening on port " + port); + } catch (IOException e) { + e.printStackTrace(); + } + new Thread(this).start(); + } + + /** + * Runs this server. There is typically no need to call this method, as the object's constructor + * creates a new thread and runs this object automatically. + */ + public void run() { + try { + ServerSocket serverSocket = new ServerSocket(url.getPort()); + while (true) { + // Create one script per socket connection. + // This is purposefully not caching the Script + // so that the script source file can be changed on the fly, + // as each connection is made to the server. + //FIXME: Groovy has other mechanisms specifically for watching to see if source code changes. + // We should probably be using that here. + // See also the comment about the fact we recompile a script that can't change. + Script script = groovy.parse(source); + new GroovyClientConnection(script, autoOutput, serverSocket.accept()); + } + } catch (Exception e) { + e.printStackTrace(); + } + } + + static class GroovyClientConnection implements Runnable { + private Script script; + private Socket socket; + private BufferedReader reader; + private PrintWriter writer; + private boolean autoOutputFlag; + + GroovyClientConnection(Script script, boolean autoOutput,Socket socket) throws IOException { + this.script = script; + this.autoOutputFlag = autoOutput; + this.socket = socket; + reader = new BufferedReader(new InputStreamReader(socket.getInputStream())); + writer = new PrintWriter(socket.getOutputStream()); + new Thread(this, "Groovy client connection - " + socket.getInetAddress().getHostAddress()).start(); + } + public void run() { + try { + String line = null; + script.setProperty("out", writer); + script.setProperty("socket", socket); + script.setProperty("init", Boolean.TRUE); + while ((line = reader.readLine()) != null) { + // System.out.println(line); + script.setProperty("line", line); + Object o = script.run(); + script.setProperty("init", Boolean.FALSE); + if (o != null) { + if ("success".equals(o)) { + break; // to close sockets gracefully etc... + } else { + if (autoOutputFlag) { + writer.println(o); + } + } + } + writer.flush(); + } + } catch (IOException e) { + e.printStackTrace(); + } finally { + try { + writer.flush(); + writer.close(); + } finally { + try { + socket.close(); + } catch (IOException e3) { + e3.printStackTrace(); + } + } + } + } + } +} http://git-wip-us.apache.org/repos/asf/groovy/blob/10110145/src/main/groovy/groovy/util/AbstractFactory.java ---------------------------------------------------------------------- diff --git a/src/main/groovy/groovy/util/AbstractFactory.java b/src/main/groovy/groovy/util/AbstractFactory.java new file mode 100644 index 0000000..54e68e1 --- /dev/null +++ b/src/main/groovy/groovy/util/AbstractFactory.java @@ -0,0 +1,63 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one + * or more contributor license agreements. See the NOTICE file + * distributed with this work for additional information + * regarding copyright ownership. The ASF licenses this file + * to you 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 groovy.util; + +import groovy.lang.Closure; + +import java.util.Map; + +/** + * @author <a href="mailto:[email protected]">Andres Almiray</a> + * @author Danno Ferrin + */ +public abstract class AbstractFactory implements Factory { + public boolean isLeaf() { + return false; + } + + public boolean isHandlesNodeChildren() { + return false; + } + + public void onFactoryRegistration(FactoryBuilderSupport builder, String registeredName, String group) { + // do nothing + } + + public boolean onHandleNodeAttributes( FactoryBuilderSupport builder, Object node, + Map attributes ) { + return true; + } + + public boolean onNodeChildren( FactoryBuilderSupport builder, Object node, Closure childContent) { + return true; + } + + public void onNodeCompleted( FactoryBuilderSupport builder, Object parent, Object node ) { + // do nothing + } + + public void setParent( FactoryBuilderSupport builder, Object parent, Object child ) { + // do nothing + } + + public void setChild( FactoryBuilderSupport builder, Object parent, Object child ) { + // do nothing + } + +} http://git-wip-us.apache.org/repos/asf/groovy/blob/10110145/src/main/groovy/groovy/util/BufferedIterator.java ---------------------------------------------------------------------- diff --git a/src/main/groovy/groovy/util/BufferedIterator.java b/src/main/groovy/groovy/util/BufferedIterator.java new file mode 100644 index 0000000..6fa50a9 --- /dev/null +++ b/src/main/groovy/groovy/util/BufferedIterator.java @@ -0,0 +1,31 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one + * or more contributor license agreements. See the NOTICE file + * distributed with this work for additional information + * regarding copyright ownership. The ASF licenses this file + * to you 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 groovy.util; + +import java.util.Iterator; + +/** + * An iterator that allows examining the next element without consuming it. + * + * @author Andrew Taylor + * @since 2.5.0 + */ +public interface BufferedIterator<T> extends Iterator<T> { + T head(); +} http://git-wip-us.apache.org/repos/asf/groovy/blob/10110145/src/main/groovy/groovy/util/BuilderSupport.java ---------------------------------------------------------------------- diff --git a/src/main/groovy/groovy/util/BuilderSupport.java b/src/main/groovy/groovy/util/BuilderSupport.java new file mode 100644 index 0000000..f634f1f --- /dev/null +++ b/src/main/groovy/groovy/util/BuilderSupport.java @@ -0,0 +1,228 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one + * or more contributor license agreements. See the NOTICE file + * distributed with this work for additional information + * regarding copyright ownership. The ASF licenses this file + * to you 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 groovy.util; + +import groovy.lang.Closure; +import groovy.lang.GroovyObjectSupport; +import groovy.lang.GroovyRuntimeException; +import groovy.lang.MissingMethodException; +import org.codehaus.groovy.runtime.InvokerHelper; + +import java.util.List; +import java.util.Map; + +/** + * An abstract base class for creating arbitrary nested trees of objects + * or events + * + * @author <a href="mailto:[email protected]">James Strachan</a> + */ +public abstract class BuilderSupport extends GroovyObjectSupport { + + private Object current; + private Closure nameMappingClosure; + private final BuilderSupport proxyBuilder; + + public BuilderSupport() { + this.proxyBuilder = this; + } + + public BuilderSupport(BuilderSupport proxyBuilder) { + this(null, proxyBuilder); + } + + public BuilderSupport(Closure nameMappingClosure, BuilderSupport proxyBuilder) { + this.nameMappingClosure = nameMappingClosure; + this.proxyBuilder = proxyBuilder; + } + + /** + * Convenience method when no arguments are required + * + * @param methodName the name of the method to invoke + * @return the result of the call + */ + public Object invokeMethod(String methodName) { + return invokeMethod(methodName, null); + } + + public Object invokeMethod(String methodName, Object args) { + Object name = getName(methodName); + return doInvokeMethod(methodName, name, args); + } + + protected Object doInvokeMethod(String methodName, Object name, Object args) { + Object node = null; + Closure closure = null; + List list = InvokerHelper.asList(args); + + //System.out.println("Called invokeMethod with name: " + name + " arguments: " + list); + + switch (list.size()) { + case 0: + node = proxyBuilder.createNode(name); + break; + case 1: { + Object object = list.get(0); + if (object instanceof Map) { + node = proxyBuilder.createNode(name, (Map) object); + } else if (object instanceof Closure) { + closure = (Closure) object; + node = proxyBuilder.createNode(name); + } else { + node = proxyBuilder.createNode(name, object); + } + } + break; + case 2: { + Object object1 = list.get(0); + Object object2 = list.get(1); + if (object1 instanceof Map) { + if (object2 instanceof Closure) { + closure = (Closure) object2; + node = proxyBuilder.createNode(name, (Map) object1); + } else { + node = proxyBuilder.createNode(name, (Map) object1, object2); + } + } else { + if (object2 instanceof Closure) { + closure = (Closure) object2; + node = proxyBuilder.createNode(name, object1); + } else if (object2 instanceof Map) { + node = proxyBuilder.createNode(name, (Map) object2, object1); + } else { + throw new MissingMethodException(name.toString(), getClass(), list.toArray(), false); + } + } + } + break; + case 3: { + Object arg0 = list.get(0); + Object arg1 = list.get(1); + Object arg2 = list.get(2); + if (arg0 instanceof Map && arg2 instanceof Closure) { + closure = (Closure) arg2; + node = proxyBuilder.createNode(name, (Map) arg0, arg1); + } else if (arg1 instanceof Map && arg2 instanceof Closure) { + closure = (Closure) arg2; + node = proxyBuilder.createNode(name, (Map) arg1, arg0); + } else { + throw new MissingMethodException(name.toString(), getClass(), list.toArray(), false); + } + } + break; + default: { + throw new MissingMethodException(name.toString(), getClass(), list.toArray(), false); + } + + } + + if (current != null) { + proxyBuilder.setParent(current, node); + } + + if (closure != null) { + // push new node on stack + Object oldCurrent = getCurrent(); + setCurrent(node); + // let's register the builder as the delegate + setClosureDelegate(closure, node); + try { + closure.call(); + } catch (Exception e) { + throw new GroovyRuntimeException(e); + } + setCurrent(oldCurrent); + } + + proxyBuilder.nodeCompleted(current, node); + return proxyBuilder.postNodeCompletion(current, node); + } + + /** + * A strategy method to allow derived builders to use + * builder-trees and switch in different kinds of builders. + * This method should call the setDelegate() method on the closure + * which by default passes in this but if node is-a builder + * we could pass that in instead (or do something wacky too) + * + * @param closure the closure on which to call setDelegate() + * @param node the node value that we've just created, which could be + * a builder + */ + protected void setClosureDelegate(Closure closure, Object node) { + closure.setDelegate(this); + } + + protected abstract void setParent(Object parent, Object child); + + protected abstract Object createNode(Object name); + + protected abstract Object createNode(Object name, Object value); + + protected abstract Object createNode(Object name, Map attributes); + + protected abstract Object createNode(Object name, Map attributes, Object value); + + /** + * A hook to allow names to be converted into some other object + * such as a QName in XML or ObjectName in JMX. + * + * @param methodName the name of the desired method + * @return the object representing the name + */ + protected Object getName(String methodName) { + if (nameMappingClosure != null) { + return nameMappingClosure.call(methodName); + } + return methodName; + } + + + /** + * A hook to allow nodes to be processed once they have had all of their + * children applied. + * + * @param node the current node being processed + * @param parent the parent of the node being processed + */ + protected void nodeCompleted(Object parent, Object node) { + } + + /** + * A hook to allow nodes to be processed once they have had all of their + * children applied and allows the actual node object that represents + * the Markup element to be changed + * + * @param node the current node being processed + * @param parent the parent of the node being processed + * @return the node, possibly new, that represents the markup element + */ + protected Object postNodeCompletion(Object parent, Object node) { + return node; + } + + protected Object getCurrent() { + return current; + } + + protected void setCurrent(Object current) { + this.current = current; + } +}
