/******************************************************************************* * Copyright © 2011, 2013 IBM Corporation and others. * All rights reserved. This program and the accompanying materials * are made available under the terms of the Eclipse Public License v1.0 * which accompanies this distribution, and is available at * http://www.eclipse.org/legal/epl-v10.html * * Contributors: * IBM Corporation - initial API and implementation * *******************************************************************************/ package org.eclipse.edt.gen; import java.lang.reflect.Method; import java.util.ArrayList; import java.util.Enumeration; import java.util.HashMap; import java.util.HashSet; import java.util.List; import java.util.Locale; import java.util.Map; import java.util.ResourceBundle; import java.util.Set; import java.util.Stack; import org.eclipse.edt.compiler.internal.interfaces.IGenerationMessageRequestor; import org.eclipse.edt.gen.EGLMessages.AccumulatingGenerationMessageRequestor; import org.eclipse.edt.mof.EObject; import org.eclipse.edt.mof.codegen.api.TabbedWriter; import org.eclipse.edt.mof.codegen.api.Template; import org.eclipse.edt.mof.codegen.api.TemplateContext; import org.eclipse.edt.mof.codegen.api.TemplateException; import org.eclipse.edt.mof.egl.Annotation; import org.eclipse.edt.mof.egl.Classifier; import org.eclipse.edt.mof.egl.Element; import org.eclipse.edt.mof.egl.IrFactory; import org.eclipse.edt.mof.egl.Stereotype; import org.eclipse.edt.mof.egl.StructPart; import org.eclipse.edt.mof.egl.TryStatement; import org.eclipse.edt.mof.egl.Type; import org.eclipse.edt.mof.egl.utils.TypeUtils; @SuppressWarnings("serial") public abstract class EglContext extends TemplateContext { private IrFactory factory; // this is the nesting of exit/continue labels for various statements private Stack<Label> labelStack; // This is the nesting of try statements. private Stack<TryStatement> tryStack; // this is the user overrideable set of properties files values private Map<String, String> primitiveTypeMappings; private Map<String, String> nativeTypeMappings; private Map<String, String> messageMappings; private List<String> supportedPartTypes; private List<String> supportedStereotypes; // the message requestor private IGenerationMessageRequestor messageRequestor; // This is used by nextTempName. private int tempIndex; private Annotation lastStatementLocation; private final Set<String> requiredRuntimeContainers = new HashSet<String>(); public EglContext(AbstractGeneratorCommand processor) { factory = IrFactory.INSTANCE; tryStack = new Stack<TryStatement>(); labelStack = new Stack<Label>(); messageRequestor = new AccumulatingGenerationMessageRequestor(); // handle the loads ClassLoader loader = processor.getClass().getClassLoader(); nativeTypeMappings = load(processor.getNativeTypes(), loader); primitiveTypeMappings = load(processor.getPrimitiveTypes(), loader); messageMappings = load(processor.getEGLMessages(), loader); supportedPartTypes = processor.getSupportedPartTypes(); supportedStereotypes = processor.getSupportedStereotypes(); } /** * Returns the factory for creating Egl model element instances. * @return the IrFactory */ public IrFactory getFactory() { return factory; } /** * @param String The exit and continue statement labels we expect to be pushing on the stack */ public void pushLabelStack(Label label) { this.labelStack.push(label); } public void popLabelStack() { this.labelStack.pop(); } /** * try and locate the first entry beginning with the passed string and return the value or null */ public Label searchLabelStack(int type) { int i = this.labelStack.size(); // we are interested in scanning lifo for the entries while (i > 0) { Label label = this.labelStack.get(--i); // are we trying to match the nearest generic or a specific one if (type == Label.LABEL_TYPE_GENERIC || label.getType() == type) return label; } return null; } /** * try and locate the first entry beginning with the passed string and return the value or null */ public Label searchLabelStack(int type, int ignore) { int i = this.labelStack.size(); // we are interested in scanning lifo for the entries while (i > 0) { Label label = this.labelStack.get(--i); // are we trying to match the nearest generic, ignoring a specific type if (type == Label.LABEL_TYPE_GENERIC && label.getType() != ignore) return label; } return null; } /** * Empty the try stack */ public void clearTryStack() { this.tryStack.clear(); } /** * Checks if the stack is empty or not * @return true if the stack has no elements */ public boolean tryStackIsEmpty() { return tryStack.isEmpty(); } /** * @param stmt The TryStatement to push on the stack */ public void pushTryStack(TryStatement stmt) { this.tryStack.push(stmt); } /** * @param stmt The TryStatement we expect to be popping off the stack */ public void popTryStack(TryStatement stmt) { this.tryStack.pop(); } /** * Returns a String that can be used as the name of a temporary variable. It will be "eze$Temp" plus a number. The number * is 1 the first time this method is called, then 2, then 3, and so on. */ public int nextTempIndex() { tempIndex++; return tempIndex; } /** * Returns a String that can be used as the name of a temporary variable. It will be "eze$Temp" plus a number. The number * is 1 the first time this method is called, then 2, then 3, and so on. */ public String nextTempName() { return Constants.temporaryVariablePrefix + nextTempIndex(); } /** * Returns a String that can be used as the name of a temporary variable. It will be "eze$LNNTemp" plus a number. The number * is 1 the first time this method is called, then 2, then 3, and so on. */ public String nextLogicallyNotNullableTempName() { return Constants.temporaryVariableLogicallyNotNullablePrefix + nextTempIndex(); } public IGenerationMessageRequestor getMessageRequestor() { return messageRequestor; } public void setMessageRequestor(IGenerationMessageRequestor requestor) { this.messageRequestor = requestor; } public Map<String, String> getMessageMapping() { return messageMappings; } public boolean mapsToPrimitiveType(Type type) { return primitiveTypeMappings.containsKey(type.getClassifier().getTypeSignature()); } public String getPrimitiveMapping(String item) { String value = primitiveTypeMappings.get(item); if (value != null) return value; else return null; } public String getPrimitiveMapping(Type type) { String value = primitiveTypeMappings.get(type.getClassifier().getTypeSignature()); if (value != null) return value; else { handleValidationError(type); throw new TemplateException("", type); } } public boolean mapsToNativeType(Type type) { return nativeTypeMappings.containsKey(type.getClassifier().getTypeSignature()); } public String getNativeMapping(String item) { String value = nativeTypeMappings.get(item); if (value != null) return value; else return null; } public String getNativeImplementationMapping(Type type) { // the nativetypes mapping file works like this: // if there are no entries in the table, then the type signature will be used for both interface and implementation // names. // if there is 1 entry in the table, then it is defined as interfacename=implementationname (where the interface name // and the model name are the same) // if there are 2 entries in the table, then it is defined as modelname=interfacename and // interfacename=implementationname(where the interface name and the model name are not the same) // // in this case, we are looking for the implementation name. The model name is type.getTypeSignature(). Check to see // if there is an entry in the table. If there is, then it either points at the interface name (if there is a 2nd // entry), or the implementation name (if there is no 2nd entry) String value = nativeTypeMappings.get(type.getClassifier().getTypeSignature()); if (value != null) { // there was an entry, so we need to see if there is a 2nd entry. Return the target of either the 2nd entry (if // exists) or the 1st entry as the implementation name String impl = nativeTypeMappings.get(value); if (impl != null) return impl; else return value; } else // there was no entry, so default interface name to type signature (model name) return type.getClassifier().getTypeSignature(); } public String getNativeInterfaceMapping(Type type) { // the nativetypes mapping file works like this: // if there are no entries in the table, then the type signature will be used for both interface and implementation // names. // if there is 1 entry in the table, then it is defined as interfacename=implementationname (where the interface name // and the model name are the same) // if there are 2 entries in the table, then it is defined as modelname=interfacename and // interfacename=implementationname(where the interface name and the model name are not the same) // // in this case, we are looking for the interface name. The model name is type.getTypeSignature(). Check to see if // there is an entry in the table. If there is, then it either points at the interface name (if there is a 2nd // entry), or the implementation name (if there is no 2nd entry) String value = nativeTypeMappings.get(type.getClassifier().getTypeSignature()); if (value != null) { // there was an entry, so we need to see if there is a 2nd entry. If there is only 1 entry, then return interface // name as type signature (model name). If there is a 2nd name, then return interface name from the // modelname=interfacename if (nativeTypeMappings.get(value) != null) return value; else return type.getClassifier().getTypeSignature(); } else // there was no entry, so default interface name to type signature (model name) return type.getClassifier().getTypeSignature(); } public Object getParameter(String internalName) { Object value = null; if (this.get(internalName) != null) value = ((CommandParameter) this.get(internalName)).getValue(); return value; } public void foreachAnnotation(List<? extends Annotation> list, char separator, String methodName, EglContext ctx, TabbedWriter out, Object... args) throws TemplateException { for (int i = 0; i < list.size(); i++) { this.invoke(methodName, list.get(i), ctx, out, args); if (i < list.size() - 1) { out.print(separator); out.print(' '); } } } public void foreachType(List<? extends Type> list, char separator, String methodName, EglContext ctx, TabbedWriter out, Object... args) throws TemplateException { for (int i = 0; i < list.size(); i++) { this.invoke(methodName, list.get(i), ctx, out, args); if (i < list.size() - 1) { out.print(separator); out.print(' '); } } } // these methods allows you to add annotation data to any object. we cannot alter the IR's by adding annotations, so this // allows us to collect data associated with any object and maintain that through the generation @SuppressWarnings("unchecked") public Object getAttribute(Object key, String value) { // check to see if the key exists. if it does, then it will be associated with a list object. that list object will // be searched for the value. if found, then that value is returned List<Annotation> list = (List<Annotation>) this.get(key); if (list == null) return null; Annotation annotation = findAnnotation(value, list); if (annotation != null) { return annotation.getValue(); } else { return null; } } private Annotation findAnnotation(String value, List<Annotation> list) { // we need to search the list of annotations looking for the string value, then return the associated data value for (int i = 0; i < list.size(); i++) { Annotation annotation = list.get(i); if (value.equalsIgnoreCase(annotation.getEClass().getName())) return annotation; } return null; } @SuppressWarnings("unchecked") public void putAttribute(Object key, String value, Object entry) { // check to see if the key exists. if this doesn't, then create the list object List<Annotation> list = (List<Annotation>) this.get(key); if (list == null) { list = new ArrayList<Annotation>(); this.put(key, list); } // create the annotation Annotation annotation = findAnnotation(value, list); if (annotation == null) { annotation = factory.createAnnotation(value); // add the annotation to the list list.add(annotation); } annotation.setValue(entry); } public Map<String, String> load(String fileList, ClassLoader loader) { Map<String, String> map = new HashMap<String, String>(); // the fileList will be 1 or more locations for the properties files involved with this implementation. If more than // 1 location is in the list, then it will be separated by a semi-colon. We need to split these out into individual // locations and process them in order. String[] files = fileList.length() == 0 ? new String[0] : fileList.split("[;]"); for (String file : files) { // process this property file ResourceBundle bundle = ResourceBundle.getBundle(file, Locale.getDefault(), loader); Enumeration<String> keys = bundle.getKeys(); while (keys.hasMoreElements()) { String key = keys.nextElement(); String data = bundle.getString(key); // if this entry is already in the map, don't replace it. This keeps the list such that the 1st // entry that we come across remains as the entry to use. if (map.get(key) == null) map.put(key, data); } } return map; } public abstract void handleValidationError(Element ex); public abstract void handleValidationError(Annotation ex); public abstract void handleValidationError(Type ex); public Object invoke(String methodName, Type type, Object... args) { TemplateMethod tm = getTemplateMethod(methodName, type, args); if (tm == null) { tm = getTemplateMethod(methodName, type.getEClass(), args); } if (tm != null) { return doInvoke(tm.getMethod(), tm.getTemplate(), type, args); } else { return invoke(methodName, (Object) type, args); } } public Object invokeSuper(Template template, String methodName, Type type, Object... args) { // Get Classifier associated with the given template Classifier classifier = getClassifierForTemplate(template); StructPart superType = null; if (classifier instanceof StructPart && !((StructPart) classifier).getSuperTypes().isEmpty()) { superType = ((StructPart) classifier).getSuperTypes().get(0); } if (superType == null) { return invokeSuper(template, methodName, (EObject) type, args); } TemplateMethod tm = getTemplateMethod(methodName, superType, args); // if the method points back at itself, we have a recursive loop. In that case type 1 more level of supertype if (tm != null && tm.getTemplate().getClass().toString().equals(template.getClass().toString())) tm = getTemplateMethod(methodName, ((StructPart) superType.getClassifier()).getSuperTypes().get(0), args); if (tm != null) { return doInvoke(tm.getMethod(), tm.getTemplate(), type, args); } else { return invokeSuper(template, methodName, (EObject) type, args); } } public TemplateMethod getTemplateMethod(String methodName, Type type, Object... args) throws TemplateException { TemplateMethod tm = null; Method method = null; Template template = null; template = getTemplateForClassifier(type.getClassifier()); // If no template found try Stereotype based lookup if (template == null) { Stereotype stereotype = type.getClassifier().getStereotype(); if (stereotype != null) { template = getTemplateForEClassifier(stereotype.getEClass()); } } if (template == null && type instanceof StructPart) { for (StructPart part : ((StructPart) type).getSuperTypes()) { tm = getTemplateMethod(methodName, part, args); if (tm != null) break; } } else if (template != null) { Class<?> classifierClass = type.getClass(); method = primGetMethod(methodName, template.getClass(), classifierClass, args); if (method != null) { return new TemplateMethod(template, method); } if (tm == null && type instanceof StructPart) { for (StructPart part : ((StructPart) type).getSuperTypes()) { tm = getTemplateMethod(methodName, part, args); if (tm != null) break; } } } return tm; } public Classifier getClassifierForTemplate(Template template) throws TemplateException { Classifier result = null; String signature = getTemplateKey(template); result = (Classifier) TypeUtils.getEGLType(signature); return result; } public Template getTemplateForClassifier(Classifier clazz) { return getTemplateRaw(clazz.getTypeSignature()); } public Annotation getLastStatementLocation() { return lastStatementLocation; } public void setLastStatementLocation(Annotation lastStatementLocation) { this.lastStatementLocation = lastStatementLocation; } /** * Adds a runtime container to be added to the build path when generation is finished. * @param id The runtime container id to be added. */ public void requireRuntimeContainer(String id) { requiredRuntimeContainers.add(id); } public Set<String> getRequiredRuntimeContainers() { return requiredRuntimeContainers; } public List<String> getSupportedPartTypes() { return supportedPartTypes; } public List<String> getSupportedStereotypes() { return supportedStereotypes; } }