/*
* Copyright 2009-2017 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.tests.internal;
import static org.junit.Assert.assertTrue;
import java.io.File;
import java.util.ArrayList;
import java.util.HashMap;
import java.util.Iterator;
import java.util.List;
import java.util.Map;
import org.codehaus.groovy.runtime.DefaultGroovyMethods;
import org.codehaus.jdt.groovy.model.GroovyCompilationUnit;
import org.eclipse.jdt.core.groovy.tests.search.InferencingTestSuite;
import org.junit.Assert;
/**
* Represents a test workload for the inferencer consisting of a number of inferencing tasks to be executed
* all against the same compilation unit contents.
*/
public class InferencerWorkload implements Iterable<InferencerWorkload.InferencerTask> {
private static final String BEG_MARK_START = "/*!";
private static final String BEG_MARK_SEPARATOR = ":" ;
private static final String BEG_MARK_END = "!*/" ;
private static final String END_MARK = "/*!*/" ;
private static final Map<String, String> DEFAULT_ALIASES = new HashMap<String, String>();
static {
DEFAULT_ALIASES.put("B", "java.lang.Byte");
DEFAULT_ALIASES.put("C", "java.lang.Character");
DEFAULT_ALIASES.put("D", "java.lang.Double");
DEFAULT_ALIASES.put("F", "java.lang.Float");
DEFAULT_ALIASES.put("I", "java.lang.Integer");
DEFAULT_ALIASES.put("L", "java.lang.Long");
DEFAULT_ALIASES.put("S", "java.lang.Short");
DEFAULT_ALIASES.put("V", "java.lang.Void");
DEFAULT_ALIASES.put("Z", "java.lang.Boolean");
DEFAULT_ALIASES.put("STR", "java.lang.String");
DEFAULT_ALIASES.put("LIST", "java.util.List");
DEFAULT_ALIASES.put("MAP", "java.util.Map");
DEFAULT_ALIASES.put("O", "java.lang.Object");
}
/**
* Represents a single inferencing 'task' in a workload. Contains information
* about the location we want to inference the type for and the expected result
* for that location.
*/
public class InferencerTask {
public final int start;
public final int end;
public final String expectedResultType;
public final String expectedDeclaringType;
public InferencerTask(int start, int end, String expectResultType, String expectDeclaringType) {
this.start = start;
this.end = end;
this.expectedResultType = expectResultType;
this.expectedDeclaringType = expectDeclaringType;
}
/**
* Contents of the file in which we are trying to do inference.
*/
public String getContents() {
return InferencerWorkload.this.getContents();
}
@Override
public String toString() {
return "Type: " + expectedResultType + "\nDeclaring: " + expectedDeclaringType + "\nContents: " + getContents().substring(start, end);
}
}
private List<InferencerTask> tasks;
private String contents;
private final Map<String,String> aliases;
private boolean aliasesLocked = false; //Set to true when we start parsing the workloadDefinition
@SuppressWarnings("deprecation")
public InferencerWorkload(File workloadDefinitionFile, String ... extraAliases) throws Exception {
this(DefaultGroovyMethods.getText(workloadDefinitionFile), extraAliases);
}
/**
* Creates a workload from a 'definition'. The definition is the contents of some groovy file with
* additional marker 'tags' inserted that contain the expected result and|or declaring type.
* <p>
* The tags will be stripped out during initialisation of the workload.
* <p>
* Tags look like (without the backslashes): <pre>\/*!ResultType:DeclaringType!*\/expression\/*!*\/</pre>.
* <p>
* In order to cut down on the length of the type specifications, there are aliases.
* Default aliases are specified by {@value #DEFAULT_ALIASES}, but you can add your own
* using the extraAliases argument. It takes pairs of strings (alias, long name).
* So, the length of extraAliases must be even.
*/
public InferencerWorkload(String workloadDefinition, String ... extraAliases) {
aliases = new HashMap<String, String>(DEFAULT_ALIASES);
for (int i = 0; i < extraAliases.length; i++) {
defAlias(extraAliases[i++], extraAliases[i]);
}
aliasesLocked = true; // Should allow changing aliases anymore from here onward.
StringBuilder stripped = new StringBuilder(); // The contents of the file minus the tags.
tasks = new ArrayList<InferencerWorkload.InferencerTask>();
int readPos = 0; //Boundary between processed and unprocessed input in workloadDefinition
while (readPos >= 0 && readPos < workloadDefinition.length() ) {
int headStart = workloadDefinition.indexOf(BEG_MARK_START, readPos);
int separator = -1;
int headEnd = -1;
int tail = -1;
if (headStart>=0) {
separator = workloadDefinition.indexOf(BEG_MARK_SEPARATOR, headStart);
headEnd = workloadDefinition.indexOf(BEG_MARK_END, headStart);
tail = workloadDefinition.indexOf(END_MARK, headStart);
}
//Well formatted tag looks like this:
// <**resultType:declType**>expression<***>
//So if one was found, then the various positions of found markers must be in a specific order:
if (headStart>=0 && separator>headStart && headEnd>separator && tail>headEnd) {
//Copy text in front of tag into 'stripped' contents buffer
int start = readPos;
int end = headStart;
stripped.append(workloadDefinition.substring(start, end));
//Extract resultType:
start = headStart + BEG_MARK_START.length();
end = separator;
String resultType = workloadDefinition.substring(start, end);
if (aliases.containsKey(resultType)) {
resultType = aliases.get(resultType);
}
if (resultType.length() == 0) {
resultType = null;
}
//Extract declType
start = separator+BEG_MARK_SEPARATOR.length();
end = headEnd;
String declType = workloadDefinition.substring(start, end);
if (aliases.containsKey(declType)) {
declType = aliases.get(declType);
}
if (declType.length() == 0) {
declType = null;
}
//Extract expression
start = headEnd+BEG_MARK_END.length();
end = tail;
String expression = workloadDefinition.substring(start, end);
//Copy expression and compute start and end positions in 'stripped' buffer.
start = stripped.length();
stripped.append(expression);
end = stripped.length();
tasks.add(new InferencerTask(start, end, resultType, declType));
readPos = tail + END_MARK.length();
} else {
//No tag was found so we are done, but don't forget to copy remaining text
stripped.append(workloadDefinition.substring(readPos));
readPos = -1;
}
}
contents = stripped.toString();
}
protected void defAlias(String name, String expansion) {
Assert.assertTrue("Aliases must be defined *before* parsing the workload", !aliasesLocked);
String existing = aliases.get(name);
if (existing!=null) {
Assert.fail("Multiple definitions for alias "+name+" first = "+expansion+" second = "+expansion);
} else {
aliases.put(name, expansion);
}
}
/**
* @return The text of the file, without the workload marker tags.
*/
public String getContents() {
return contents;
}
public Iterator<InferencerTask> iterator() {
return tasks.iterator();
}
/**
* Performs inferencing on the given compilation unit.
* It is assumed that the contents of the compilation unit
* matches the contents of this inferencer task
*/
public void perform(GroovyCompilationUnit unit, boolean assumeNoUnknowns) throws Exception {
boolean doneSomething = false;
try {
unit.becomeWorkingCopy(null);
StringBuilder sb = new StringBuilder();
for (InferencerTask task : this) {
doneSomething = true;
String res = InferencingTestSuite.checkType(unit, task.start, task.end, task.expectedResultType, task.expectedDeclaringType, assumeNoUnknowns, false);
if (res != null) {
sb.append("\n\nInferencing failure:\n" + res);
}
// only look for unknowns the first time
assumeNoUnknowns = false;
}
if (sb.length() > 0) {
Assert.fail(sb.toString());
}
} finally {
unit.discardWorkingCopy();
}
assertTrue("Workload should have at least one annotated element", doneSomething);
}
public void perform(GroovyCompilationUnit unit) throws Exception {
perform(unit, false);
}
}