/** * Copyright (c) 2009-2011, The HATS Consortium. All rights reserved. * This file is licensed under the terms of the Modified BSD License. */ package org.absmodels.abs.plugin.builder; import java.io.File; import java.io.FileInputStream; import java.io.IOException; import java.io.InputStreamReader; import java.lang.reflect.InvocationTargetException; import java.util.ArrayList; import java.util.HashSet; import java.util.List; import java.util.Map; import java.util.Map.Entry; import java.util.Properties; import java.util.Set; import org.absmodels.abs.plugin.Activator; import org.absmodels.abs.plugin.console.ConsoleManager; import org.absmodels.abs.plugin.console.MsgConsole; import org.absmodels.abs.plugin.editor.outline.PackageAbsFile; import org.absmodels.abs.plugin.editor.outline.PackageContainer; import org.absmodels.abs.plugin.editor.outline.PackageEntry; import org.absmodels.abs.plugin.editor.reconciling.AbsModelManager; import org.absmodels.abs.plugin.editor.reconciling.AbsModelManagerImpl; import org.absmodels.abs.plugin.internal.IncrementalModelBuilder; import org.absmodels.abs.plugin.internal.NoModelException; import org.absmodels.abs.plugin.internal.TypecheckInternalException; import org.eclipse.core.resources.*; import org.eclipse.core.runtime.CoreException; import org.eclipse.core.runtime.IProgressMonitor; import org.eclipse.core.runtime.Path; import org.eclipse.jface.preference.IPersistentPreferenceStore; import org.eclipse.ui.actions.WorkspaceModifyOperation; import org.eclipse.ui.preferences.ScopedPreferenceStore; import abs.frontend.analyser.SemanticCondition; import abs.frontend.analyser.SemanticConditionList; import abs.frontend.ast.ASTNode; import abs.frontend.ast.CompilationUnit; import abs.frontend.ast.Model; import abs.frontend.parser.Main; import abs.frontend.parser.ParserError; import abs.frontend.parser.SyntaxError; import abs.frontend.typechecker.locationtypes.LocationType; import abs.frontend.typechecker.locationtypes.infer.LocationTypeInferrerExtension; import abs.frontend.typechecker.locationtypes.infer.LocationTypeVariable; import static org.absmodels.abs.plugin.util.Constants.*; import static org.absmodels.abs.plugin.util.UtilityFunctions.*; public class AbsNature implements IProjectNature { private IProject project; private final IncrementalModelBuilder modelbuilder = new IncrementalModelBuilder(); private AbsModelManager modelManager = new AbsModelManagerImpl(this); public static final String PACKAGE_DEPENDENCIES = ".dependencies"; public static final String READONLY_PACKAGE_SUFFIX = "-readonly"; private final PackageContainer packageContainer = new PackageContainer(); /** * the lock for access to the model. Should always be synchronized with when accessing the * model in the model builder in some way */ public volatile Object modelLock = new Object(); // TODO: Should probably be final, but then the mocks fail. private ScopedPreferenceStore preferencestore; /** * The default console singleton used by getDefaultJavaConsole() */ private MsgConsole defaultJavaConsole = null; /** * The default console singleton used by getDefaultSDEditConsole() */ private MsgConsole defaultSDEditConsole = null; /** * The default console singleton used by getMaudeConsole() */ private MsgConsole defaultMaudeConsole = null; /** * The default console singleton used by getMaudeConsole() */ private MsgConsole defaultABSUnitTestExecutionConsole = null; /** * The default console singleton used by getMavenConsole() */ private MsgConsole defaultMavenConsole = null; /** * Gives the default console for Java Output * @return Java Message Console */ public MsgConsole getJavaConsole(){ if (defaultJavaConsole == null){ defaultJavaConsole = ConsoleManager.newConsole("Java Output (" + getProject().getName() + ")"); } return defaultJavaConsole; } /** * Gives the default console for SDEdit Output * @return SDEdit Message Console */ public MsgConsole getSDEditConsole(){ if (defaultSDEditConsole == null){ defaultSDEditConsole = ConsoleManager.newConsole("SDEdit Output (" + getProject().getName() + ")"); } return defaultSDEditConsole; } /** * Gives the default console for Maude Output * @return Maude Message Console */ public MsgConsole getMaudeConsole(){ if (defaultMaudeConsole == null){ defaultMaudeConsole = ConsoleManager.newConsole("Maude Output (" + getProject().getName() + ")"); } return defaultMaudeConsole; } /** * Gives the default console for ABSUnit Test Execution Output * @return ABSUnit Test Execution Message Console */ public MsgConsole getABSUnitTestExecutionConsole(){ if (defaultABSUnitTestExecutionConsole == null){ defaultABSUnitTestExecutionConsole = ConsoleManager.newConsole("ABSUnit Test Execution Output (" + getProject().getName() + ")"); } return defaultABSUnitTestExecutionConsole; } /** * Gives the default console for Maven Output * @return Maven Message Console */ public MsgConsole getMavenConsole(){ if (defaultMavenConsole == null){ defaultMavenConsole = ConsoleManager.newConsole("Maven Output (" + getProject().getName() + ")"); } return defaultMavenConsole; } /** * adds the current builder for abs files to the project. Is only called once when the * nature is initially assigned to the project. * * @see org.eclipse.core.resources.IProjectNature#configure() */ @Override public void configure() throws CoreException { IProjectDescription desc = project.getDescription(); ICommand[] commands = desc.getBuildSpec(); for (int i = 0; i < commands.length; ++i) { if (commands[i].getBuilderName().equals(BUILDER_ID)) { return; } } ICommand[] newCommands = new ICommand[commands.length + 1]; System.arraycopy(commands, 0, newCommands, 0, commands.length); ICommand command = desc.newCommand(); command.setBuilderName(BUILDER_ID); newCommands[newCommands.length - 1] = command; desc.setBuildSpec(newCommands); project.setDescription(desc, null); } /** * removes the builder for abs files from the project. Gets called only when * removing the nature from the project. * * @see org.eclipse.core.resources.IProjectNature#deconfigure() */ @Override public void deconfigure() throws CoreException { IProjectDescription description = getProject().getDescription(); ICommand[] commands = description.getBuildSpec(); for (int i = 0; i < commands.length; ++i) { if (commands[i].getBuilderName().equals(BUILDER_ID)) { ICommand[] newCommands = new ICommand[commands.length - 1]; System.arraycopy(commands, 0, newCommands, 0, i); System.arraycopy(commands, i + 1, newCommands, i, commands.length - i - 1); description.setBuildSpec(newCommands); project.setDescription(description, null); return; } } } @Override public IProject getProject() { return project; } @Override public void setProject(IProject project) { this.project = project; // initialise package dependencies initDependencies(); } public void createMarkers(Model model) throws CoreException { createMarkers(model.getErrors()); createMarkers(model.getTypeErrors()); } public void createMarkers(SemanticConditionList errors) throws CoreException { for (SemanticCondition e : errors) { createMarker(e); } } public void createMarker(SemanticCondition error) throws CoreException { createMarker(error.getNode(), error.getMsg(), error.isError() ? IMarker.SEVERITY_ERROR : IMarker.SEVERITY_WARNING, TYPECHECK_MARKER_TYPE); } /** * take a node of the AST, finds the corresponding compilation unit and adds a marker for the message to the file the * compilation unit was parsed from * @param node some AST node which is part of a compilation unit * @param message the message to be shown in the marker pop up * @param severity {@link IMarker#SEVERITY_ERROR} {@link IMarker#SEVERITY_WARNING} {@link IMarker#SEVERITY_INFO} * @param markerType the id of the marker that should be created * @throws CoreException {@link IMarker#setAttribute(String, boolean)} {@link IMarker#getAttribute(String, int)} */ private static void createMarker( ASTNode<?> node, String message, int severity, String markerType) throws CoreException { if (node == null) return; IFile declfile = ResourcesPlugin.getWorkspace().getRoot().getFileForLocation(new Path((node.getCompilationUnit()).getFileName())); /* [stolz] I had a situation where I had the ABSFrontend in the workspace, and then closed it: org.eclipse.core.internal.resources.ResourceException: Resource '/ABSFrontend/src/abs/lang/abslang.abs' does not exist. at org.eclipse.core.internal.resources.Resource.checkExists(Resource.java:320) at org.eclipse.core.internal.resources.Resource.checkAccessible(Resource.java:194) at org.eclipse.core.internal.resources.Resource.createMarker(Resource.java:711) at org.abs-models.abs.plugin.builder.AbsNature.createMarker(AbsNature.java:241) FIXME: We shouldn't have picked that one up in the first place, I guess, but better be safe than sorry. */ if(declfile==null || !declfile.isAccessible()) return; assert declfile.isAccessible(); IMarker marker = declfile.createMarker(markerType); marker.setAttribute(IMarker.MESSAGE, message); marker.setAttribute(IMarker.SEVERITY, severity); marker.setAttribute(START_LINE, node.getStartLine()-1); marker.setAttribute(START_COLUMN, node.getStartColumn()-1); marker.setAttribute(END_LINE, node.getEndLine()-1); marker.setAttribute(END_COLUMN, node.getEndColumn()); marker.setAttribute(IMarker.LINE_NUMBER, node.getStartLine()); } /** * parses the given resource with the given model builder. The resource is only parsed if it is an abs file * @param resource the abs file * @param withincomplete include incomplete expressions into the AST? * @param monitor */ public void parseABSFile(IResource resource, final boolean withincomplete, IProgressMonitor monitor) { if (resource.exists() && isABSFile(resource)) { final IFile file = (IFile) resource; assert file.exists(); try { // Markers modify the workspace: new WorkspaceModifyOperation() { @Override protected void execute(IProgressMonitor monitor) throws CoreException, InvocationTargetException, InterruptedException { /* Only delete PARSE-markers first: if we've just been launched, * we don't want to erase persistent markers, since type-errors etc. * only come back through an explicit build, which doesn't happen * on launching Eclipse even with auto-build. [stolz] */ file.deleteMarkers(PARSE_MARKER_TYPE, true, IResource.DEPTH_ZERO); try { if (!file.isSynchronized(IResource.DEPTH_ZERO)) { file.refreshLocal(IResource.DEPTH_ZERO, monitor); } Main m = new Main(); m.setWithStdLib(true); m.setAllowIncompleteExpr(withincomplete); List<CompilationUnit> units = new ArrayList<CompilationUnit>(); if (isABSPackage(file)) { units.addAll(m.parseABSPackageFile(file.getLocation().toFile())); } else { CompilationUnit cu = m.parseUnit(file.getLocation().toFile(), null, new InputStreamReader(file.getContents())); cu.setName(file.getLocation().toFile().getAbsolutePath()); units.add(cu); } modelbuilder.addCompilationUnits(units); for (CompilationUnit cu : units) { if(cu.hasParserErrors()){ for(ParserError err : cu.getParserErrors()){ addMarker(file, err); } } } } catch(NoModelException e){ //ignore } catch (CoreException e) { throw e; }catch (Exception e) { throw new InvocationTargetException(e); } } }.run(monitor); } catch (InvocationTargetException e) { Activator.logException(e); } catch (InterruptedException e) { } } else assert false : resource; } public boolean toIncludeInScope(IResource resource) { if (project == null) return false; IFolder target = project.getFolder("target"); if (! target.exists()) { return true; } boolean ignore = getProjectPreferenceStore().getBoolean(MAVEN_IGNORE_TARGET_FOLDER); return !ignore || !target.getProjectRelativePath().isPrefixOf(resource.getProjectRelativePath()); } public static void addMarker(IResource file, ParserError err) throws CoreException { int startline = err.getLine()-1; int startcolumn = err.getColumn()-1; int endline; int endcolumn; endcolumn = -1; endline = startline; String message = err.getMessage(); int severity = IMarker.SEVERITY_ERROR; IMarker marker = file.createMarker(PARSE_MARKER_TYPE); marker.setAttribute(IMarker.MESSAGE, message); marker.setAttribute(IMarker.SEVERITY, severity); marker.setAttribute(START_LINE, startline); marker.setAttribute(START_COLUMN, startcolumn); marker.setAttribute(END_LINE, endline); marker.setAttribute(END_COLUMN, endcolumn); marker.setAttribute(IMarker.LINE_NUMBER, startline+1); } /** * takes the properties from the project preference store for location type checking and location type precision. Typeckecks * the current model in the current model builder. * Note that your model must be sufficiently "complete" and not have any semantic errors . * @param monitor * @throws CoreException {@link IResource#deleteMarkers(String, boolean, int)} does not handle exceptions thrown by * #createMarker(SemanticError) and #createMarker(TypecheckInternalException) */ void typeCheckModel(IProgressMonitor monitor) throws CoreException{ getProject().deleteMarkers(TYPECHECK_MARKER_TYPE, true, IResource.DEPTH_INFINITE); getProject().deleteMarkers(LOCATION_TYPE_INFERENCE_MARKER_TYPE, true, IResource.DEPTH_INFINITE); boolean dolocationtypecheck = getProjectPreferenceStore().getBoolean(LOCATION_TYPECHECK); String defaultlocationtype = getProjectPreferenceStore().getString(DEFAULT_LOCATION_TYPE); String defaultlocationtypeprecision = getProjectPreferenceStore().getString(LOCATION_TYPE_PRECISION); boolean checkProducts = getProjectPreferenceStore().getBoolean(PRODUCT_TYPECHECK); try { addPackagesForTypeChecking(); final SemanticConditionList typeerrors = modelbuilder.typeCheckModel(monitor,dolocationtypecheck, defaultlocationtype, defaultlocationtypeprecision, checkProducts); createMarkers(typeerrors); if (dolocationtypecheck) { createLocationTypeInferenceMarker(); } } catch (NoModelException e) { //ignore return; } catch (TypecheckInternalException e) { /* Internal error caught. Log, and turn into an error marker */ Activator.logException(e); createMarker(e); return; } } /** * Add ABS package dependencies to {@link AbsNature#modelbuilder} for type checking * @throws TypecheckInternalException */ private void addPackagesForTypeChecking() throws TypecheckInternalException { try { Main m = new Main(); m.setWithStdLib(true); m.setAllowIncompleteExpr(true); List<CompilationUnit> units = new ArrayList<CompilationUnit>(); for (PackageEntry entry : packageContainer.getPackages()) { File file = new File(entry.getPath()); if (isABSPackage(file)) { units.addAll(m.parseABSPackageFile(file)); } } modelbuilder.addCompilationUnits(units); } catch (IOException e) { throw new TypecheckInternalException(e); } catch (NoModelException e) { //ignore } } /** * retrieves the result of the type inference from the modelbuilders * @return the result of the type inference or <b>null</b> if type inference is off or * an exception occurred while type inference was in progress */ public Map<LocationTypeVariable, LocationType> getLocationTypeInferrerResult(){ synchronized (modelLock) { LocationTypeInferrerExtension locationTypeInferrerExtension = modelbuilder.getLocationTypeInferrerExtension(); if (locationTypeInferrerExtension != null) { Map<LocationTypeVariable, LocationType> inferResult = locationTypeInferrerExtension.getResults(); return inferResult; } else { return null; } } } private void createLocationTypeInferenceMarker() throws CoreException { synchronized (modelLock) { LocationTypeInferrerExtension locationTypeInferrerExtension = modelbuilder.getLocationTypeInferrerExtension(); if (locationTypeInferrerExtension != null) { Map<LocationTypeVariable, LocationType> inferResult = locationTypeInferrerExtension.getResults(); if (inferResult != null) { if (getProjectPreferenceStore().getBoolean(LOCATION_TYPE_OVERLAY)) { for (Entry<LocationTypeVariable, LocationType> e : inferResult.entrySet()) { LocationType annoType = e.getKey().getAnnotatedType(); if (annoType == null || annoType.equals(LocationType.INFER)) { int severity = IMarker.SEVERITY_ERROR; if (e.getValue().isNear()) { severity = IMarker.SEVERITY_INFO; } else if (e.getValue().isSomewhere()) { severity = IMarker.SEVERITY_WARNING; } createMarker(e.getKey().getNode(), "Inferred Location Type: " + e.getValue(), severity , LOCATION_TYPE_INFERENCE_MARKER_TYPE); } } } } else { IMarker marker = getProject().createMarker(TYPECHECK_MARKER_TYPE); marker.setAttribute(IMarker.MESSAGE, "Location Type Inference Failed"); marker.setAttribute(IMarker.SEVERITY, IMarker.SEVERITY_ERROR); } } } } /** * adds a marker for the TypecheckInternalException to the current project corresponding to this narture * @param e the exception from type checking the model * @throws CoreException {@link IResource#createMarker(String)} */ private void createMarker(TypecheckInternalException e) throws CoreException { IMarker exceptionMarker = project.createMarker(TYPECHECK_MARKER_TYPE); exceptionMarker.setAttribute(IMarker.MESSAGE, "Exception while typechecking: "+e.getMessage()); exceptionMarker.setAttribute(IMarker.SEVERITY, IMarker.SEVERITY_ERROR); } public void removeCompilationUnit(IResource file){ CompilationUnit cu = getCompilationUnit(file); try { modelbuilder.removeCompilationUnit(cu); } catch (NoModelException e) { //ignore } } /** * retrieves the compilationUnit for the given file from the modelbuilder. * * @param curFile * @return the comilationUnit for the file or <b>null</b> if the model is not (yet) parsed */ public CompilationUnit getCompilationUnit(IResource curFile){ assert curFile != null; return getCompilationUnit(curFile.getLocation().toFile().getAbsolutePath()); } /** * retrieves the compilationUnit for the file with the given absoluteFilePath from the modelbuilder * @param absoluteFilePath * @return */ public CompilationUnit getCompilationUnit(String absoluteFilePath){ try { return modelbuilder.getCompilationUnit(absoluteFilePath); } catch (NoModelException e) { return null; } } public void cleanModel(){ modelbuilder.cleanModel(); } public Model getCompleteModel(){ return getCompleteModel(null); } public Model getCompleteModel(IProgressMonitor mon){ Model model = modelbuilder.getCompleteModel(); /* Not compiled yet? */ if (model == null && project != null) { try { project.build(IncrementalProjectBuilder.FULL_BUILD, mon); // Now it should be ready: model = modelbuilder.getCompleteModel(); } catch (CoreException e) { Activator.logException(e); } } return model; } /** * creates and returns the singleton for the project-wide preference store * @return the project preference store */ public IPersistentPreferenceStore getProjectPreferenceStore(){ if(preferencestore == null){ preferencestore = new ScopedPreferenceStore(new ProjectScope(getProject()), PLUGIN_ID); initProjectDefaults(); } return preferencestore; } /** * Set the defaults for the properties in the project property pages */ private void initProjectDefaults(){ preferencestore.setDefault(LOCATION_TYPECHECK, true); preferencestore.setDefault(PRODUCT_TYPECHECK, true); preferencestore.setDefault(LOCATION_TYPE_OVERLAY, true); preferencestore.setDefault(DEFAULT_LOCATION_TYPE, DEFAULT_DEFAULT_LOCATION_TYPE); preferencestore.setDefault(LOCATION_TYPE_PRECISION, DEFAULT_LOCATION_TYPE_PRECISION); if (project.getFile(DEFAULT_MAVEN_POM_PATH).exists()) { preferencestore.setDefault(MAUDE_PATH, DEFAULT_MAVEN_TARGET_MAUDE_PATH); preferencestore.setDefault(JAVA_SOURCE_PATH, DEFAULT_MAVEN_TARGET_JAVA_PATH); } else { preferencestore.setDefault(MAUDE_PATH, DEFAULT_MAUDE_PATH); preferencestore.setDefault(JAVA_SOURCE_PATH, DEFAULT_JAVA_PATH); } preferencestore.setDefault(NO_WARNINGS, true); preferencestore.setDefault(SOURCE_ONLY, false); preferencestore.setDefault(ALWAYS_COMPILE, true); /* * Maven */ preferencestore.setDefault(MAVEN_EXEC_PATH, DEFAULT_MAVEN_EXEC_PATH); preferencestore.setDefault(MAVEN_IGNORE_TARGET_FOLDER, false); } public void initDependencies() { try { if (project != null) { packageContainer.clear(); File file = new File(project.getFile(PACKAGE_DEPENDENCIES).getLocationURI()); Set<PackageEntry> entries = new HashSet<PackageEntry>(); if (file.exists()) { Properties prop = new Properties(); prop.loadFromXML(new FileInputStream(file)); for (String qualified : prop.stringPropertyNames()) { Boolean readonly = Boolean.valueOf(prop.getProperty(qualified)); File f = new File(qualified); if (isABSPackage(f)) { entries.add(new PackageEntry(packageContainer, f.getName(), qualified, readonly)); } } packageContainer.setPackages(entries); packageContainer.setProject(project); } } } catch (IOException e) { Activator.logException(e); } } public PackageContainer getPackages() { return packageContainer; } /** * @author stolz */ public void emptyModel() { modelbuilder.addCompilationUnit(null); // Hack to get stdlib } /** * @deprecated unused */ public void parseABSFile(PackageAbsFile file, boolean withincomplete, Object monitor) { Main m = new Main(); m.setWithStdLib(true); m.setAllowIncompleteExpr(withincomplete); List<CompilationUnit> units = new ArrayList<CompilationUnit>(); try { final File f = new File(file.getAbsoluteFilePath()); assert f.exists(); units.addAll(m.parseABSPackageFile(f)); modelbuilder.addCompilationUnits(units); } catch (IOException e) { Activator.logException(e); } catch (NoModelException e) { } } public AbsModelManager getModelManager() { return modelManager; } }