/**
* 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.
*
* See the NOTICE file distributed with this work for additional
* information regarding copyright ownership.
*/
package org.thingml.compilers;
import org.eclipse.emf.ecore.util.EcoreUtil;
import org.fusesource.jansi.Ansi;
import org.sintef.thingml.*;
import org.sintef.thingml.helpers.AnnotatedElementHelper;
import org.sintef.thingml.helpers.ConfigurationHelper;
import org.sintef.thingml.helpers.ThingMLElementHelper;
import org.thingml.compilers.spi.ExternalThingPlugin;
import org.thingml.compilers.spi.NetworkPlugin;
import org.thingml.compilers.spi.SerializationPlugin;
import java.io.File;
import java.io.InputStream;
import java.io.PrintWriter;
import java.io.UnsupportedEncodingException;
import java.util.*;
import org.apache.commons.io.FileUtils;
public class Context {
public Instance currentInstance;
// Store the output of the compilers. The key is typically a file name but finer grained generatedCode may also be used by the compilers.
protected Map<String, StringBuilder> generatedCode = new HashMap<String, StringBuilder>();
protected Map<String, File> filesToCopy = new HashMap<String, File>();
boolean debugTraceWithID = false;
Map<Integer, String> debugStrings;
private ThingMLCompiler compiler;
private Configuration currentConfiguration;
// Any any annotation to the context
private Map<String, String> contextAnnotations = new HashMap<String, String>();
/**
* *****************************************************************************************
* Keyword protection API. To be used by all compilers which need to protect against clashes
* with target language keywords
* ******************************************************************************************
*/
private Set<String> keywords = new HashSet<String>();
private String preKeywordEscape = "`";
private String postKeywordEscape = "`";
private File outputDirectory = null;
private Boolean atInitTimeLock = false;
public Ansi ansi = new Ansi();
public Context(ThingMLCompiler compiler) {
this.debugStrings = new HashMap<Integer, String>();
this.compiler = compiler;
}
public Context(ThingMLCompiler compiler, String... keywords) {
this.debugStrings = new HashMap<Integer, String>();
this.compiler = compiler;
for (String k : keywords) {
this.keywords.add(k);
}
}
//Some Helpers to overcome bug in EMF related to broken equals
public boolean containsInstance(List<Instance> list, Instance element) {
for (Instance e : list) {
if (EcoreUtil.equals(e, element))
return true;
}
return false;
}
public boolean containsInstance(Set<Instance> list, Instance element) {
for (Instance e : list) {
if (EcoreUtil.equals(e, element))
return true;
}
return false;
}
public boolean containsMessage(Set<Message> list, Message element) {
for (Message e : list) {
if (EcoreUtil.equals(e, element))
return true;
}
return false;
}
public boolean containsAllInstances(List<Instance> thisList, List<Instance> thatList) {
for (Instance e : thatList) {
if (!containsInstance(thisList, e))
return false;
}
return true;
}
public boolean containsParam(List<Parameter> list, Parameter element) {
for (Parameter e : list) {
if (EcoreUtil.equals(e, element))
return true;
}
return false;
}
public ThingMLCompiler getCompiler() {
return compiler;
}
public Configuration getCurrentConfiguration() {
return currentConfiguration;
}
public void setCurrentConfiguration(Configuration currentConfiguration) {
this.currentConfiguration = currentConfiguration;
}
/**
* @param path (relative to outputDir) where the code should be generated
* @return a StringBuilder where the code can be built
*/
public StringBuilder getBuilder(String path) {
StringBuilder b = generatedCode.get(path);
if (b == null) {
b = new StringBuilder();
generatedCode.put(path, b);
}
return b;
}
/**
* This one will be removed when we figure out why it is here
*
* @param path
* @return
*/
@Deprecated
public StringBuilder getNewBuilder(String path) {
StringBuilder b = new StringBuilder();
generatedCode.put(path, b);
return b;
}
/**
* Dumps the whole code generated in the generatedCode
*/
public void writeGeneratedCodeToFiles() {
for (Map.Entry<String, StringBuilder> e : generatedCode.entrySet()) {
writeTextFile(e.getKey(), e.getValue().toString());
}
}
/**
* @param path (relative to outputDir) where the file should be copied
* @param source The source file co vopy
*/
public void addFileToCopy(String path, File source) {
if (filesToCopy.containsKey(path)) {
File original = filesToCopy.get(path);
if (!source.getAbsoluteFile().equals(original.getAbsoluteFile()))
throw new Error("The output file to copy to (" + path + ") is already added but with a different source (" + original.getAbsolutePath() + ").");
} else {
filesToCopy.put(path, source.getAbsoluteFile());
}
}
/**
* Copies files in the filesystem to the output directory
*/
public void copyFilesToOutput() {
for (Map.Entry<String, File> e : filesToCopy.entrySet()) {
File source = e.getValue();
File destination = openOutputFile(e.getKey());
if (destination.exists()) {
System.err.println("[WARNING] The output file to copy to already exists, overwriting. (" + destination.getAbsolutePath() + ").");
} else if (!source.exists()) {
System.err.println("[WARNING] The output file to copy from doesn't exists, skipping. (" + source.getAbsolutePath() + ").");
} else {
try {
FileUtils.copyFile(source, destination);
} catch (Exception ex) {
System.err.println("Problem while copying file");
ex.printStackTrace();
}
}
}
}
/********************************************************************************************
* Helper functions reused by different compilers
********************************************************************************************/
/**
* Allows to create files in the output directory either for writing or copying
*
* @param path
*/
public File openOutputFile(String path) {
try {
File file = new File(getOutputDirectory(), path);
if (!file.getParentFile().exists())
file.getParentFile().mkdirs();
return file;
} catch (Exception ex) {
System.err.println("Problem while creating output file (" + getOutputDirectory() + "/" + path + ").");
ex.printStackTrace();
return null;
}
}
/**
* Allows to writeTextFile additional files (not generated in the normal generatedCode)
*
* @param path
* @param content
*/
public void writeTextFile(String path, String content) {
try {
File file = openOutputFile(path);
PrintWriter w = new PrintWriter(file);
w.print(content);
w.close();
} catch (Exception ex) {
System.err.println("Problem while dumping the code");
ex.printStackTrace();
}
}
public String getTemplateByID(String template_id) {
final InputStream input = this.getClass().getClassLoader().getResourceAsStream(template_id);
String result = null;
try {
if (input != null) {
result = org.apache.commons.io.IOUtils.toString(input, java.nio.charset.Charset.forName("UTF-8"));
input.close();
} else {
System.out.println("[Error] Template not found: " + template_id);
}
} catch (Exception e) {
//e.printStackTrace();
return null; // the template was not found
}
return result;
}
public void addMarker(String marker) {
addContextAnnotation(marker, marker);
}
public void removerMarker(String marker) {
removeContextAnnotation(marker);
}
public void addContextAnnotation(String key, String value) {
contextAnnotations.put(key, value);
}
public String getContextAnnotation(String key) {
return contextAnnotations.get(key);
}
public String removeContextAnnotation(String key) {
return contextAnnotations.remove(key);
}
public boolean hasContextAnnotation(String key, String value) {
return contextAnnotations.containsKey(key) && contextAnnotations.get(key).equals(value);
}
public boolean hasContextAnnotation(String key) {
return contextAnnotations.containsKey(key);
}
/**
* @param value, a String of at least a character
* @return value with first letter in upper case
*/
public String firstToUpper(String value) {
if (value == null)
return null;
else if (value.length() > 1)
return value.substring(0, 1).toUpperCase() + value.substring(1);
else
return value.substring(0, 1).toUpperCase();
}
public String getVariableName(Variable var) {
return ThingMLElementHelper.qname(var, "_") + "_var";
}
public String getInstanceName(Instance i) {
return i.getType().getName() + "_" + i.getName();
}
public String getInstanceName(Connector c) {
StringBuilder builder = new StringBuilder();
builder.append("c_");
if (c.getName() != null)
builder.append(c.getName());
builder.append("_");
builder.append(getInstanceName(c.getCli().getInstance()) + "-" + c.getRequired());
builder.append("_to_");
builder.append(getInstanceName(c.getSrv().getInstance()) + "-" + c.getProvided());
return builder.toString();
}
public Set<String> getKeywords() {
return keywords;
}
public String getPreKeywordEscape() {
return preKeywordEscape;
}
public void setPreKeywordEscape(String preKeywordEscape) {
this.preKeywordEscape = preKeywordEscape;
}
public String getPostKeywordEscape() {
return postKeywordEscape;
}
//Debug traces
public void setPostKeywordEscape(String postKeywordEscape) {
this.postKeywordEscape = postKeywordEscape;
}
/**
* @param value, to be escaped
* @return the escaped (if need be) value
*/
public String protectKeyword(String value) {
if (keywords.contains(value)) {
return preKeywordEscape + value + postKeywordEscape;
} else {
return value;
}
}
public File getOutputDirectory() {
if (outputDirectory == null) return compiler.getOutputDirectory();
else return outputDirectory;
}
public void setOutputDirectory(File outDir) {
outDir.mkdirs();
if (!outDir.exists())
throw new Error("ERROR: The output directory does not exist (" + outDir.getAbsolutePath() + ").");
if (!outDir.isDirectory())
throw new Error("ERROR: The output directory has to be a directory (" + outDir.getAbsolutePath() + ").");
if (!outDir.canWrite())
throw new Error("ERROR: The output directory is not writable (" + outDir.getAbsolutePath() + ").");
outputDirectory = outDir.getAbsoluteFile();
}
public File getInputDirectory() {
return compiler.getInputDirectory();
}
public boolean getDebugWithID() {
return debugTraceWithID;
}
public void setDebugWithID(boolean b) {
debugTraceWithID = b;
}
public String traceOnEntry(Thing t, StateMachine sm) {
if (!debugTraceWithID) {
return " (" + t.getName() + "): Enters " + sm.getName();
} else {
return null;
}
}
public String traceOnEntry(Thing t, Region r, State s) {
if (!debugTraceWithID) {
return " (" + t.getName() + "): Enters " + r.getName() + ":" + s.getName();
} else {
return null;
}
}
public String traceOnExit(Thing t, Region r, State s) {
if (!debugTraceWithID) {
return " (" + t.getName() + "): Exits " + r.getName() + ":" + s.getName();
} else {
return null;
}
}
public String traceSendMessage(Thing t, Port p, Message m) {
if (!debugTraceWithID) {
return " (" + t.getName() + "): " + p.getName() + "!" + m.getName();
} else {
return null;
}
}
public String traceReceiveMessage(Thing t, Port p, Message m) {
if (!debugTraceWithID) {
return " (" + t.getName() + "): " + p.getName() + "?" + m.getName();
} else {
return null;
}
}
public String traceFunctionBegin(Thing t, Function f) {
if (!debugTraceWithID) {
return " (" + t.getName() + "): Start " + f.getName();
} else {
return null;
}
}
public String traceFunctionDone(Thing t, Function f) {
if (!debugTraceWithID) {
return " (" + t.getName() + "): " + f.getName() + " Done.";
} else {
return null;
}
}
public String traceTransition(Thing t, Transition tr, Port p, Message m) {
if (!debugTraceWithID) {
if (p != null) {
return " (" + t.getName()
+ "): transition "
+ tr.getSource().getName()
+ " -> " + tr.getTarget().getName() + " event "
+ p.getName() + "?"
+ m.getName();
} else {
return traceTransition(t, tr);
}
} else {
return null;
}
}
public String traceTransition(Thing t, Transition tr) {
if (!debugTraceWithID) {
return " (" + t.getName()
+ "): transition "
+ tr.getSource().getName()
+ " -> " + tr.getTarget().getName();
} else {
return null;
}
}
public String traceInternal(Thing t, Port p, Message m) {
if (!debugTraceWithID) {
if (p != null) {
return " (" + t.getName() + "): internal event " + p.getName() + "?" + m.getName();
} else {
return traceInternal(t);
}
} else {
return null;
}
}
public String traceInternal(Thing t) {
if (!debugTraceWithID) {
return " (" + t.getName() + "): internal";
} else {
return null;
}
}
public String traceInit(Thing t) {
if (!debugTraceWithID) {
return " (" + t.getName() + "): Init";
} else {
return null;
}
}
public Boolean getAtInitTimeLock() {
return atInitTimeLock;
}
public void generateFixedAtInitValue(Configuration cfg, Instance inst, Expression a, StringBuilder builder) {
atInitTimeLock = true;
currentInstance = inst;
getCompiler().getThingActionCompiler().generate(a, builder, this);
atInitTimeLock = false;
}
public void initSerializationPlugins(Configuration cfg) {
for (SerializationPlugin sp : this.getCompiler().getSerializationPlugins()) {
sp.setConfiguration(cfg);
sp.setContext(this);
}
}
public void generateNetworkLibs(Configuration cfg) {
initSerializationPlugins(cfg);
Set<Protocol> protocols = new HashSet<>();
for (ExternalConnector eco : ConfigurationHelper.getExternalConnectors(cfg)) {
if (!protocols.contains(eco.getProtocol())) {
protocols.add(eco.getProtocol());
}
}
for (Protocol p : protocols) {
final NetworkPlugin netPlugin = this.getCompiler().getNetworkPlugin(p);
if (netPlugin != null)
netPlugin.addProtocol(p);
}
for (NetworkPlugin np : this.getCompiler().getNetworkPlugins()) {
if (!np.getAssignedProtocols().isEmpty()) {
np.generateNetworkLibrary(cfg, this);
}
}
List<Thing> things = ExternalThingPlugin.getAllExternalThings(ConfigurationHelper.allThings(cfg));
Set<ExternalThingPlugin> externalPluginLibs = new HashSet<ExternalThingPlugin>();
for(Thing thing : things) {
ExternalThingPlugin etp = this.getCompiler().getExternalThingPlugin(thing);
if(etp != null) {
List<PlatformAnnotation> annotations = AnnotatedElementHelper.allAnnotations(thing);
etp.addExternalThingAnnotations(new HashSet<PlatformAnnotation>(annotations));
externalPluginLibs.add(etp);
}
}
for(ExternalThingPlugin etp : externalPluginLibs)
etp.generateExternalLibrary(cfg, this);
}
public SerializationPlugin getSerializationPlugin(Protocol p) throws UnsupportedEncodingException {
if (AnnotatedElementHelper.hasAnnotation(p, "serializer")) {
final String serID = AnnotatedElementHelper.annotation(p, "serializer").get(0);
final SerializationPlugin sp = this.getCompiler().getSerializationPlugin(serID);
sp.setProtocol(p);
if (sp != null) {
return sp;
} else {
throw new UnsupportedEncodingException("Serialization plugin " + serID + " is not loaded. Please make sure it appears in resources/META-INF.services");
}
} else {
throw new UnsupportedEncodingException("Protocol " + p.getName() + " has no @serializer annotation. Please provide one in your ThingML file!");
}
}
}