/* * #%~ * VDM Code Generator * %% * Copyright (C) 2008 - 2014 Overture * %% * This program is free software: you can redistribute it and/or modify * it under the terms of the GNU General Public License as * published by the Free Software Foundation, either version 3 of the * License, or (at your option) any later version. * * This program is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU General Public License for more details. * * You should have received a copy of the GNU General Public * License along with this program. If not, see * <http://www.gnu.org/licenses/gpl-3.0.html>. * #~% */ package org.overture.codegen.vdm2java; import java.io.File; import java.io.StringWriter; import java.util.Collections; import java.util.HashSet; import java.util.LinkedList; import java.util.List; import java.util.Map; import java.util.Set; import org.apache.log4j.Logger; import org.overture.ast.analysis.AnalysisException; import org.overture.ast.definitions.ABusClassDefinition; import org.overture.ast.definitions.ACpuClassDefinition; import org.overture.ast.definitions.AExplicitOperationDefinition; import org.overture.ast.definitions.ASystemClassDefinition; import org.overture.ast.definitions.PDefinition; import org.overture.ast.definitions.SClassDefinition; import org.overture.ast.expressions.PExp; import org.overture.ast.lex.Dialect; import org.overture.ast.modules.AModuleModules; import org.overture.ast.node.INode; import org.overture.ast.statements.AIdentifierStateDesignator; import org.overture.codegen.analysis.vdm.NameCollector; import org.overture.codegen.analysis.vdm.Renaming; import org.overture.codegen.analysis.vdm.VarRenamer; import org.overture.codegen.analysis.vdm.VarShadowingRenameCollector; import org.overture.codegen.analysis.violations.GeneratedVarComparison; import org.overture.codegen.analysis.violations.InvalidNamesResult; import org.overture.codegen.analysis.violations.ReservedWordsComparison; import org.overture.codegen.analysis.violations.TypenameComparison; import org.overture.codegen.analysis.violations.VdmAstAnalysis; import org.overture.codegen.analysis.violations.Violation; import org.overture.codegen.assistant.AssistantManager; import org.overture.codegen.ir.CodeGenBase; import org.overture.codegen.ir.IRConstants; import org.overture.codegen.ir.IRStatus; import org.overture.codegen.ir.IrNodeInfo; import org.overture.codegen.ir.PIR; import org.overture.codegen.ir.SExpIR; import org.overture.codegen.ir.VdmNodeInfo; import org.overture.codegen.ir.analysis.DepthFirstAnalysisAdaptor; import org.overture.codegen.ir.declarations.ADefaultClassDeclIR; import org.overture.codegen.ir.declarations.AInterfaceDeclIR; import org.overture.codegen.ir.declarations.AModuleDeclIR; import org.overture.codegen.ir.declarations.SClassDeclIR; import org.overture.codegen.merging.MergeVisitor; import org.overture.codegen.trans.DivideTrans; import org.overture.codegen.trans.ModuleToClassTransformation; import org.overture.codegen.utils.GeneralCodeGenUtils; import org.overture.codegen.utils.Generated; import org.overture.codegen.utils.GeneratedData; import org.overture.codegen.utils.GeneratedModule; import org.overture.config.Settings; public class JavaCodeGen extends CodeGenBase implements IJavaQouteEventCoordinator { public static final String TRACE_IMPORT = "org.overture.codegen.runtime.traces.*"; public static final String RUNTIME_IMPORT = "org.overture.codegen.runtime.*"; public static final String JAVA_UTIL = "java.util.*"; public static final String JAVA_TEMPLATES_ROOT_FOLDER = "JavaTemplates"; public static final String[] JAVA_RESERVED_TYPE_NAMES = { // Classes used from the Java standard library "Utils", "Record", "Long", "Double", "Character", "String", "List", "Set" }; /** * Signatures of the java.lang.Object methods:<br> * clone()<br> * equals(Object obj)<br> * finalize()<br> * getClass()<br> * hashCode()<br> * notify()<br> * notifyAll()<br> * toString()<br> * wait()<br> * wait(long timeout, int nanos)<br> * wait(long timeout) */ public static final String[] JAVA_LANG_OBJECT_METHODS = { "clone", "equals", "finalize", "getClass", "hashCode", "notify", "notifyAll", "toString", "wait" }; public static final String JAVA_MAIN_CLASS_NAME = "Main"; public static final String JAVA_QUOTES_PACKAGE = "quotes"; public static final String INVALID_NAME_PREFIX = "cg_"; private JavaFormat javaFormat; private IJavaQuoteEventObserver quoteObserver; private JavaVarPrefixManager varPrefixManager; private JavaTransSeries transSeries; private SClassDefinition mainClass; private List<Renaming> allRenamings; private InvalidNamesResult invalidNamesResult; private List<String> warnings; private Logger log = Logger.getLogger(this.getClass().getName()); public JavaCodeGen() { super(); this.varPrefixManager = new JavaVarPrefixManager(); this.quoteObserver = null; this.javaFormat = new JavaFormat(varPrefixManager, JAVA_TEMPLATES_ROOT_FOLDER, generator.getIRInfo()); this.transSeries = new JavaTransSeries(this); clearVdmAstData(); } private void clearVdmAstData() { this.mainClass = null; this.allRenamings = new LinkedList<>(); this.invalidNamesResult = new InvalidNamesResult(); this.warnings = new LinkedList<>(); } public JavaSettings getJavaSettings() { return this.javaFormat.getJavaSettings(); } public void setJavaSettings(JavaSettings javaSettings) { this.javaFormat.setJavaSettings(javaSettings); } public JavaTransSeries getTransSeries() { return this.transSeries; } public void setTransSeries(JavaTransSeries transSeries) { this.transSeries = transSeries; } public JavaFormat getJavaFormat() { return javaFormat; } public void setJavaFormat(JavaFormat javaFormat) { this.javaFormat = javaFormat; } @Override protected void clear() { super.clear(); javaFormat.clear(); transSeries.clear(); clearVdmAstData(); } @Override protected GeneratedData genVdmToTargetLang(List<IRStatus<PIR>> statuses) throws AnalysisException { List<GeneratedModule> genModules = new LinkedList<GeneratedModule>(); // Event notification statuses = initialIrEvent(statuses); List<String> userTestCases = getUserTestCases(statuses); statuses = filter(statuses, genModules, userTestCases); List<IRStatus<AModuleDeclIR>> moduleStatuses = IRStatus.extract(statuses, AModuleDeclIR.class); List<IRStatus<PIR>> modulesAsNodes = IRStatus.extract(moduleStatuses); ModuleToClassTransformation moduleTransformation = new ModuleToClassTransformation(getInfo(), transAssistant, getModuleDecls(moduleStatuses)); for (IRStatus<PIR> status : modulesAsNodes) { try { generator.applyTotalTransformation(status, moduleTransformation); } catch (org.overture.codegen.ir.analysis.AnalysisException e) { log.error("Error when generating code for module " + status.getIrNodeName() + ": " + e.getMessage()); log.error("Skipping module.."); e.printStackTrace(); } } /** * Note that this will include the system class, whereas the CPU and BUS classes have been filtered out when the * IR status was generated */ List<IRStatus<SClassDeclIR>> classStatuses = IRStatus.extract(modulesAsNodes, SClassDeclIR.class); classStatuses.addAll(IRStatus.extract(statuses, SClassDeclIR.class)); if (getJavaSettings().getJavaRootPackage() != null) { for (IRStatus<SClassDeclIR> irStatus : classStatuses) { irStatus.getIrNode().setPackage(getJavaSettings().getJavaRootPackage()); } } List<IRStatus<SClassDeclIR>> canBeGenerated = new LinkedList<IRStatus<SClassDeclIR>>(); for (IRStatus<SClassDeclIR> status : classStatuses) { if (status.canBeGenerated()) { canBeGenerated.add(status); } else { genModules.add(new GeneratedModule(status.getIrNodeName(), status.getUnsupportedInIr(), new HashSet<IrNodeInfo>(), isTestCase(status))); } } for (DepthFirstAnalysisAdaptor trans : transSeries.getSeries()) { for (IRStatus<SClassDeclIR> status : canBeGenerated) { try { if (!getInfo().getDeclAssistant().isLibraryName(status.getIrNodeName())) { generator.applyPartialTransformation(status, trans); } } catch (org.overture.codegen.ir.analysis.AnalysisException e) { log.error("Error when generating code for class " + status.getIrNodeName() + ": " + e.getMessage()); log.error("Skipping class.."); e.printStackTrace(); } } } ClassToInterfaceTrans class2interfaceTr = new ClassToInterfaceTrans(transAssistant); List<IRStatus<PIR>> tmp = IRStatus.extract(canBeGenerated); for (IRStatus<PIR> status : tmp) { try { generator.applyTotalTransformation(status, class2interfaceTr); } catch (org.overture.codegen.ir.analysis.AnalysisException e) { log.error("Error when generating code for module " + status.getIrNodeName() + ": " + e.getMessage()); log.error("Skipping module.."); e.printStackTrace(); } } canBeGenerated = IRStatus.extract(tmp, SClassDeclIR.class); List<IRStatus<AInterfaceDeclIR>> interfaceStatuses = IRStatus.extract(tmp, AInterfaceDeclIR.class); cleanup(IRStatus.extract(canBeGenerated)); // Event notification canBeGenerated = IRStatus.extract(finalIrEvent(IRStatus.extract(canBeGenerated)), SClassDeclIR.class); canBeGenerated = filter(canBeGenerated, genModules, userTestCases); List<String> skipping = new LinkedList<String>(); MergeVisitor mergeVisitor = javaFormat.getMergeVisitor(); javaFormat.setFunctionValueAssistant(transSeries.getFuncValAssist()); for (IRStatus<SClassDeclIR> status : canBeGenerated) { INode vdmClass = status.getVdmNode(); if (vdmClass == mainClass) { SClassDeclIR mainIr = status.getIrNode(); if (mainIr instanceof ADefaultClassDeclIR) { status.getIrNode().setTag(new JavaMainTag((ADefaultClassDeclIR) status.getIrNode())); } else { log.error("Expected main class to be a " + ADefaultClassDeclIR.class.getSimpleName() + ". Got: " + status.getIrNode()); } } try { if (shouldGenerateVdmNode(vdmClass)) { genModules.add(genIrModule(mergeVisitor, status)); } else { if (!skipping.contains(status.getIrNodeName())) { skipping.add(status.getIrNodeName()); } } } catch (org.overture.codegen.ir.analysis.AnalysisException e) { log.error("Error generating code for class " + status.getIrNodeName() + ": " + e.getMessage()); log.error("Skipping class.."); e.printStackTrace(); } } List<AInterfaceDeclIR> interfaces = transSeries.getFuncValAssist().getFuncValInterfaces(); for(IRStatus<AInterfaceDeclIR> a : interfaceStatuses) { interfaces.add(a.getIrNode()); } for (AInterfaceDeclIR funcValueInterface : interfaces) { funcValueInterface.setPackage(getJavaSettings().getJavaRootPackage()); try { StringWriter writer = new StringWriter(); funcValueInterface.apply(javaFormat.getMergeVisitor(), writer); String formattedJavaCode = JavaCodeGenUtil.formatJavaCode(writer.toString()); genModules.add(new GeneratedModule(funcValueInterface.getName(), funcValueInterface, formattedJavaCode, false)); } catch (org.overture.codegen.ir.analysis.AnalysisException e) { log.error("Error generating code for function value interface " + funcValueInterface.getName() + ": " + e.getMessage()); log.error("Skipping interface.."); e.printStackTrace(); } } GeneratedData data = new GeneratedData(); data.setClasses(genModules); data.setQuoteValues(generateJavaFromVdmQuotes()); data.setInvalidNamesResult(invalidNamesResult); data.setSkippedClasses(skipping); data.setAllRenamings(allRenamings); data.setWarnings(warnings); return data; } public List<GeneratedModule> generateJavaFromVdmQuotes() { try { List<String> quoteValues = generator.getQuoteValues(); if (quoteValues.isEmpty()) { return null; // Nothing to generate } javaFormat.getMergeVisitor().init(); JavaQuoteValueCreator creator = new JavaQuoteValueCreator(generator.getIRInfo(), transAssistant); List<ADefaultClassDeclIR> quoteClasses = new LinkedList<>(); for (String quoteNameVdm : quoteValues) { String pack = getJavaSettings().getJavaRootPackage(); ADefaultClassDeclIR quoteDecl = creator.consQuoteValue(quoteNameVdm, quoteNameVdm, pack); quoteClasses.add(quoteDecl); } // Event notification if (quoteObserver != null) { quoteObserver.quoteClassesProduced(quoteClasses); } List<GeneratedModule> modules = new LinkedList<GeneratedModule>(); for (int i = 0; i < quoteClasses.size(); i++) { String quoteNameVdm = quoteValues.get(i); ADefaultClassDeclIR qc = quoteClasses.get(i); StringWriter writer = new StringWriter(); qc.apply(javaFormat.getMergeVisitor(), writer); modules.add(new GeneratedModule(quoteNameVdm, qc, formatCode(writer), false)); } return modules; } catch (org.overture.codegen.ir.analysis.AnalysisException e) { log.error("Error encountered when formatting quotes: " + e.getMessage()); e.printStackTrace(); } return null; } @Override public String formatCode(StringWriter writer) { String code = writer.toString(); if (getJavaSettings().formatCode()) { code = JavaCodeGenUtil.formatJavaCode(code); } return code; } @Override protected void preProcessAst(List<INode> ast) throws AnalysisException { super.preProcessAst(ast); if (Settings.dialect == Dialect.VDM_PP) { if (getJavaSettings().getVdmEntryExp() != null) { try { mainClass = GeneralCodeGenUtils.consMainClass(getClasses(ast), getJavaSettings().getVdmEntryExp(), Settings.dialect, JAVA_MAIN_CLASS_NAME, getInfo().getTempVarNameGen()); ast.add(mainClass); } catch (Exception e) { // It can go wrong if the VDM entry point does not type check warnings.add("The chosen launch configuration could not be type checked: " + e.getMessage()); warnings.add("Skipping launch configuration.."); } } } List<INode> userModules = getUserModules(ast); allRenamings = normaliseIdentifiers(userModules); // To document any renaming of variables shadowing other variables allRenamings.addAll(performRenaming(userModules, getInfo().getIdStateDesignatorDefs())); invalidNamesResult = validateVdmModelNames(userModules); } @Override public void preProcessVdmUserClass(INode node) { super.preProcessVdmUserClass(node); if (!getJavaSettings().genJUnit4tests()) { return; } if (node instanceof SClassDefinition) { SClassDefinition clazz = (SClassDefinition) node; if (getInfo().getDeclAssistant().isTestCase(clazz)) { List<PDefinition> toRemove = new LinkedList<>(); for (PDefinition d : clazz.getDefinitions()) { if (d instanceof AExplicitOperationDefinition) { AExplicitOperationDefinition op = (AExplicitOperationDefinition) d; if (op.getName().getName().equals(IRConstants.TEST_CASE_RUN_FULL_SUITE) && op.getParameterPatterns().isEmpty()) { toRemove.add(op); } } } clazz.getDefinitions().removeAll(toRemove); } } } @Override protected void genIrStatus(List<IRStatus<PIR>> statuses, INode node) throws AnalysisException { if (!(node instanceof ACpuClassDefinition) && !(node instanceof ABusClassDefinition)) { if (node instanceof ASystemClassDefinition && !getJavaSettings().genSystemClass()) { return; } VdmAstJavaValidator v = validateVdmNode(node); if (v.hasUnsupportedNodes()) { // We can tell by analysing the VDM AST that the IR generator will produce an // IR tree that the Java backend cannot code generate String nodeName = getInfo().getDeclAssistant().getNodeName(node); HashSet<VdmNodeInfo> nodesCopy = new HashSet<VdmNodeInfo>(v.getUnsupportedNodes()); statuses.add(new IRStatus<PIR>(node, nodeName, /* no IR node */null, nodesCopy)); } else { super.genIrStatus(statuses, node); } } } private VdmAstJavaValidator validateVdmNode(INode node) throws AnalysisException { VdmAstJavaValidator validator = new VdmAstJavaValidator(generator.getIRInfo()); validator.getUnsupportedNodes().clear(); node.apply(validator); return validator; } private <T extends PIR> List<IRStatus<T>> filter(List<IRStatus<T>> statuses, List<GeneratedModule> generated, List<String> userTestCases) { List<IRStatus<T>> filtered = new LinkedList<IRStatus<T>>(); for (IRStatus<T> status : statuses) { if (status.canBeGenerated()) { filtered.add(status); } else { boolean isUserTestCase = userTestCases.contains(status.getIrNodeName()); generated.add(new GeneratedModule(status.getIrNodeName(), status.getUnsupportedInIr(), new HashSet<IrNodeInfo>(), isUserTestCase)); } } return filtered; } private List<Renaming> normaliseIdentifiers(List<INode> userModules) throws AnalysisException { NameCollector collector = new NameCollector(); for (INode node : userModules) { node.apply(collector); } Set<String> allNames = collector.namesToAvoid(); JavaIdentifierNormaliser normaliser = new JavaIdentifierNormaliser(allNames, getInfo().getTempVarNameGen()); for (INode node : userModules) { node.apply(normaliser); } VarRenamer renamer = new VarRenamer(); Set<Renaming> filteredRenamings = new HashSet<Renaming>(); for (Renaming r : normaliser.getRenamings()) { if (!getInfo().getDeclAssistant().isLibraryName(r.getLoc().getModule())) { filteredRenamings.add(r); } } for (INode node : userModules) { renamer.rename(node, filteredRenamings); } return new LinkedList<Renaming>(filteredRenamings); } private List<Renaming> performRenaming(List<INode> mergedParseLists, Map<AIdentifierStateDesignator, PDefinition> idDefs) throws AnalysisException { List<Renaming> allRenamings = new LinkedList<Renaming>(); VarShadowingRenameCollector renamingsCollector = new VarShadowingRenameCollector(generator.getIRInfo().getTcFactory(), idDefs); VarRenamer renamer = new VarRenamer(); for (INode node : mergedParseLists) { Set<Renaming> currentRenamings = renamer.computeRenamings(node, renamingsCollector); if (!currentRenamings.isEmpty()) { renamer.rename(node, currentRenamings); allRenamings.addAll(currentRenamings); } } Collections.sort(allRenamings); return allRenamings; } private List<AModuleDeclIR> getModuleDecls( List<IRStatus<AModuleDeclIR>> statuses) { List<AModuleDeclIR> modules = new LinkedList<AModuleDeclIR>(); for (IRStatus<AModuleDeclIR> status : statuses) { modules.add(status.getIrNode()); } return modules; } public Generated generateJavaFromVdmExp(PExp exp) throws AnalysisException, org.overture.codegen.ir.analysis.AnalysisException { // There is no name validation here. IRStatus<SExpIR> expStatus = generator.generateFrom(exp); if (expStatus.canBeGenerated()) { generator.applyPartialTransformation(expStatus, new DivideTrans(getInfo())); } try { return genIrExp(expStatus, javaFormat.getMergeVisitor()); } catch (org.overture.codegen.ir.analysis.AnalysisException e) { log.error("Could not generate expression: " + exp); e.printStackTrace(); return null; } } public void genJavaSourceFiles(File root, List<GeneratedModule> generatedClasses) { for (GeneratedModule classCg : generatedClasses) { if (classCg.canBeGenerated()) { genJavaSourceFile(root, classCg); } } } public void genJavaSourceFile(File root, GeneratedModule generatedModule) { File moduleOutputDir = JavaCodeGenUtil.getModuleOutputDir(root, this, generatedModule); if (moduleOutputDir == null) { return; } if (generatedModule != null && generatedModule.canBeGenerated() && !generatedModule.hasMergeErrors()) { String javaFileName = generatedModule.getName(); if (JavaCodeGenUtil.isQuote(generatedModule.getIrNode(), getJavaSettings())) { javaFileName += JavaQuoteValueCreator.JAVA_QUOTE_NAME_SUFFIX; } javaFileName += IJavaConstants.JAVA_FILE_EXTENSION; emitCode(moduleOutputDir, javaFileName, generatedModule.getContent()); } } private InvalidNamesResult validateVdmModelNames( List<INode> mergedParseLists) throws AnalysisException { AssistantManager assistantManager = generator.getIRInfo().getAssistantManager(); VdmAstAnalysis analysis = new VdmAstAnalysis(assistantManager); Set<Violation> reservedWordViolations = analysis.usesIllegalNames(mergedParseLists, new ReservedWordsComparison(IJavaConstants.RESERVED_WORDS, generator.getIRInfo(), INVALID_NAME_PREFIX)); Set<Violation> typenameViolations = analysis.usesIllegalNames(mergedParseLists, new TypenameComparison(JAVA_RESERVED_TYPE_NAMES, generator.getIRInfo(), INVALID_NAME_PREFIX)); Set<Violation> objectMethodViolations = analysis.usesIllegalNames(mergedParseLists, new ObjectMethodComparison(JAVA_LANG_OBJECT_METHODS, generator.getIRInfo(), INVALID_NAME_PREFIX)); // TODO: needs to take all of them into account String[] generatedTempVarNames = varPrefixManager.getIteVarPrefixes().GENERATED_TEMP_NAMES; Set<Violation> tempVarViolations = analysis.usesIllegalNames(mergedParseLists, new GeneratedVarComparison(generatedTempVarNames, generator.getIRInfo(), INVALID_NAME_PREFIX)); if (!reservedWordViolations.isEmpty() || !typenameViolations.isEmpty() || !tempVarViolations.isEmpty() || !objectMethodViolations.isEmpty()) { return new InvalidNamesResult(reservedWordViolations, typenameViolations, tempVarViolations, objectMethodViolations, INVALID_NAME_PREFIX); } else { return new InvalidNamesResult(); } } public boolean shouldGenerateVdmNode(INode node) { if (!super.shouldGenerateVdmNode(node)) { return false; } String name = null; if (node instanceof SClassDefinition) { name = ((SClassDefinition) node).getName().getName(); } else if (node instanceof AModuleModules) { name = ((AModuleModules) node).getName().getName(); } else { return true; } if (getJavaSettings().getModulesToSkip().contains(name)) { return false; } // for (SClassDefinition superDef : classDef.getSuperDefs()) // { // if (declAssistant.classIsLibrary(superDef)) // { // return false; // } // } return true; } @Override public void registerJavaQuoteObs(IJavaQuoteEventObserver obs) { if (obs != null && quoteObserver == null) { quoteObserver = obs; } } @Override public void unregisterJavaQuoteObs(IJavaQuoteEventObserver obs) { if (obs != null && quoteObserver == obs) { quoteObserver = null; } } public JavaVarPrefixManager getVarPrefixManager() { return varPrefixManager; } public void setVarPrefixManager(JavaVarPrefixManager varPrefixManager) { this.varPrefixManager = varPrefixManager; } }