groovy.transform.stc.MapEntryOrKeyValue Maven / Gradle / Ivy
/*
* 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;
/**
* 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.
* The result is a closure which can have the following forms:
*
* { key, value -> ...}
where key is the key of a map entry, and value the corresponding value
* { entry -> ... }
where entry is a {@link java.util.Map.Entry} map entry
* { ...}
where it is an implicit {@link java.util.Map.Entry} map entry
*
* This hint handles all those cases by picking the generics from the first argument of the method (by default).
* The options array is used to modify the behavior of this hint. Each string in the option array consists of
* a key=value pair.
*
* - argNum=index of the parameter representing the map (by default, 0)
* - index=true or false, by default false. If true, then an additional "int" parameter is added,
* for "withIndex" variants
*
* void doSomething(String str, Map<K,>V map, @ClosureParams(value=MapEntryOrKeyValue.class,options="argNum=1") Closure c) { ... }
*/
public class MapEntryOrKeyValue extends ClosureSignatureHint {
private static final ClassNode MAPENTRY_TYPE = ClassHelper.make(Map.Entry.class);
public List 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= or index= ", source.getLineNumber(), source.getColumnNumber());
}
}
return new Options(pIndex, generateIndex);
}
}
}
© 2015 - 2024 Weber Informatics LLC | Privacy Policy