/* * Copyright (c) 2005, 2009 Sven Efftinge 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: * Sven Efftinge - Initial API and implementation * Artem Tikhomirov (Borland) - Migration to OCL expressions */ package org.eclipse.gmf.internal.xpand.build; import java.io.IOException; import java.util.ArrayList; import java.util.Collection; import java.util.HashMap; import java.util.HashSet; import java.util.Map; import java.util.Set; import org.eclipse.core.resources.IFile; import org.eclipse.core.resources.IProject; import org.eclipse.core.resources.IResource; import org.eclipse.core.resources.IResourceDelta; import org.eclipse.core.resources.IResourceDeltaVisitor; import org.eclipse.core.resources.IResourceVisitor; import org.eclipse.core.resources.IncrementalProjectBuilder; import org.eclipse.core.runtime.CoreException; import org.eclipse.core.runtime.IProgressMonitor; import org.eclipse.core.runtime.OperationCanceledException; import org.eclipse.core.runtime.SubProgressMonitor; import org.eclipse.gmf.internal.xpand.Activator; import org.eclipse.gmf.internal.xpand.RootManager; import org.eclipse.gmf.internal.xpand.RootManager.RootDescription; import org.eclipse.gmf.internal.xpand.model.AnalysationIssue; import org.eclipse.gmf.internal.xpand.model.ExecutionContext; import org.eclipse.gmf.internal.xpand.model.ExecutionContextImpl; import org.eclipse.gmf.internal.xpand.model.Scope; import org.eclipse.gmf.internal.xpand.model.XpandResource; import org.eclipse.gmf.internal.xpand.util.ParserException; import org.eclipse.gmf.internal.xpand.util.XpandMarkerManager; import org.eclipse.gmf.internal.xpand.util.ParserException.ErrorLocationInfo; public class XpandBuilder extends IncrementalProjectBuilder implements RootManager.IRootChangeListener { private RootManager myRootManager; private WorkspaceModelRegistry modelRegistry; private boolean myRootsChanged = true; public static final String getBUILDER_ID() { return Activator.getId() + ".xpandBuilder"; } @Override protected void startupOnInitialize() { super.startupOnInitialize(); myRootManager = Activator.getRootManager(getProject()); myRootManager.addRootChangeListener(this); modelRegistry = new WorkspaceModelRegistry(getProject(), Activator.getWorkspaceMetamodelsResourceSet()); // TODO: unregister modelRegistry from Activator on closing the project // associated with this builder. Keeping modelRegistry registered inside // Activator produce incorrect meta-model resolution - meta-model loaded // from closed project will be returned instead of the one from // PackageRegistry. Activator.registerModelSource(modelRegistry); } @SuppressWarnings("unchecked") @Override protected IProject[] build(final int kind, final Map args, final IProgressMonitor monitor) throws CoreException { monitor.beginTask("Building " + getProject().getName() + " xpand project", 11); Map<RootDescription, Collection<IFile>> resourcesToBuild = collectResourcesToBuild(kind, new SubProgressMonitor(monitor, 1)); checkCanceled(monitor); doBuild(resourcesToBuild, new SubProgressMonitor(monitor, 10)); myRootsChanged = false; Set<IProject> referencedProjects = myRootManager.getReferencedProjects(); referencedProjects.remove(getProject()); return referencedProjects.toArray(new IProject[referencedProjects.size()]); } private Map<RootDescription, Collection<IFile>> collectResourcesToBuild(int kind, IProgressMonitor monitor) throws CoreException { if ((kind == FULL_BUILD) || haveRootsChangedSinceLastBuild()) { return fullBuild(monitor); } else { // TODO: modify this logic - only current project resources should // be built, but those having dependencies to the modified // "external" resources (resources from referenced projects) should // be rebuilt here Set<IProject> referencedProjects = myRootManager.getReferencedProjects(); referencedProjects.remove(getProject()); Collection<IResourceDelta> deltas = new ArrayList<IResourceDelta>(referencedProjects.size()); IResourceDelta projectDelta = getDelta(getProject()); if (projectDelta == null) { return fullBuild(monitor); } for (IProject next : referencedProjects) { final IResourceDelta delta = getDelta(next); if (delta == null) { return fullBuild(monitor); } deltas.add(delta); } return incrementalBuild(projectDelta, deltas, monitor); } } private void doBuild(Map<RootDescription, Collection<IFile>> resourcesToBuild, IProgressMonitor monitor) { monitor.beginTask("Building " + getProject().getName() + " xpand project", resourcesToBuild.size()); for (RootDescription rootDescription : resourcesToBuild.keySet()) { WorkspaceResourceManager resourceManager = Activator.createWorkspaceResourceManager(getProject(), rootDescription); Scope scope = new Scope(resourceManager, null, null); checkCanceled(monitor); doBuid(resourceManager, scope, resourcesToBuild.get(rootDescription), new SubProgressMonitor(monitor, 1)); } } private void doBuid(WorkspaceResourceManager resourceManager, Scope scope, Collection<IFile> xpandFiles, IProgressMonitor monitor) { monitor.beginTask("Building " + getProject().getName() + " xpand project", xpandFiles.size() * 2); for (IFile xpandFile : xpandFiles) { monitor.setTaskName("Building " + xpandFile.getProjectRelativePath().toOSString()); try { XpandResource xpandResource = resourceManager.loadXpandResource(xpandFile); checkCanceled(monitor); monitor.worked(1); ExecutionContext context = new ExecutionContextImpl(scope); final Set<AnalysationIssue> issues = new HashSet<AnalysationIssue>(); try { xpandResource.analyze(context, issues); checkCanceled(monitor); monitor.worked(1); updateMarkers(xpandFile, issues); } catch (RuntimeException ex) { Activator.logError(ex); XpandMarkerManager.addMarkers(xpandFile, new ParserException.ErrorLocationInfo(ex.toString())); } } catch (ParserException ex) { updateMarkers(xpandFile, ex.getParsingErrors()); } catch (IOException ex) { updateMarkers(xpandFile, ex); } catch (CoreException ex) { updateMarkers(xpandFile, ex); } } } public void rootsChanged(RootManager rootManager) { myRootsChanged = true; } private boolean haveRootsChangedSinceLastBuild() { return myRootsChanged; } private RootDescription getRootDescription(IFile file) { return myRootManager.getRootDescription(file); } // TODO: do not build all referenced projects on building this one - only // calls to external elements should be analyzed here. protected Map<RootDescription, Collection<IFile>> fullBuild(final IProgressMonitor monitor) throws CoreException { Set<IProject> referencedProjects = myRootManager.getReferencedProjects(); referencedProjects.add(getProject()); XpandMarkerManager.deleteMarkers(getProject()); //to delete markers from obsolete roots. monitor.beginTask(null, 1 + referencedProjects.size()); Map<RootDescription, Collection<IFile>> result = new HashMap<RootDescription, Collection<IFile>>(); try { // TODO: way to optimize it - visit not all the resources in this // project, but only those located below actual template roots for (IProject next : referencedProjects) { checkCanceled(monitor); next.accept(new XpandResourceVisitor(result, new SubProgressMonitor(monitor, 1))); } checkCanceled(monitor); modelRegistry.build(new SubProgressMonitor(monitor, 1)); } finally { monitor.done(); } return result; } protected Map<RootDescription, Collection<IFile>> incrementalBuild(final IResourceDelta projectDelta, final Collection<IResourceDelta> referencedProjectDeltas, final IProgressMonitor monitor) throws CoreException { monitor.beginTask(null, 2 + referencedProjectDeltas.size()); Map<RootDescription, Collection<IFile>> result = new HashMap<RootDescription, Collection<IFile>>(); try { for (IResourceDelta delta : referencedProjectDeltas) { checkCanceled(monitor); delta.accept(new XpandResourceVisitor(result, new SubProgressMonitor(monitor, 1))); } checkCanceled(monitor); projectDelta.accept(new XpandResourceVisitor(result, new SubProgressMonitor(monitor, 1))); checkCanceled(monitor); modelRegistry.build(projectDelta, new SubProgressMonitor(monitor, 1)); } finally { monitor.done(); } return result; } private void checkCanceled(final IProgressMonitor monitor) { if (monitor.isCanceled()) { throw new OperationCanceledException(); } } private static void updateMarkers(IFile resource, Set<AnalysationIssue> issues) { XpandMarkerManager.deleteMarkers(resource); XpandMarkerManager.addMarkers(resource, issues.toArray(new AnalysationIssue[issues.size()])); } private static void updateMarkers(IFile resource, Exception exception) { Activator.logError(exception); // perhaps, depending on exception type (Core|IO) we can decide to keep // old markers? XpandMarkerManager.deleteMarkers(resource); XpandMarkerManager.addErrorMarker(resource, exception.getMessage(), -1, -1); } private static void updateMarkers(IFile resource, ErrorLocationInfo[] parsingErrors) { XpandMarkerManager.deleteMarkers(resource); XpandMarkerManager.addMarkers(resource, parsingErrors); } private static boolean isXpand(final IFile resource) { return XpandResource.TEMPLATE_EXTENSION.equals(resource.getFileExtension()); } private class XpandResourceVisitor implements IResourceVisitor, IResourceDeltaVisitor { private final IProgressMonitor monitor; private Map<RootDescription, Collection<IFile>> description2ResourcesMap; public XpandResourceVisitor(Map<RootDescription, Collection<IFile>> description2ResourcesMap, final IProgressMonitor monitor) { this.monitor = monitor; this.description2ResourcesMap = description2ResourcesMap; } public boolean visit(final IResource resource) { if (!resource.isDerived() && (resource instanceof IFile) && isFileOfInterest((IFile) resource)) { reloadResource((IFile) resource); } monitor.worked(1); return true; } public boolean visit(final IResourceDelta delta) throws CoreException { final IResource resource = delta.getResource(); if (resource.isDerived()) { return false; } if ((resource instanceof IFile)) { IFile file = (IFile) resource; if (!isFileOfInterest(file)) { return false; } switch (delta.getKind()) { case IResourceDelta.ADDED: reloadResource(file); break; case IResourceDelta.REMOVED: handleRemovement(file); break; case IResourceDelta.CHANGED: reloadResource(file); break; } } else if (resource instanceof IProject) { // forget about project in resource manager if (delta.getKind() == IResourceDelta.REMOVED) { System.err.println("Project removed:" + resource.getName()); } if (delta.getKind() == IResourceDelta.OPEN) { System.err.println("Project open:" + ((IProject) resource).isOpen()); } } monitor.worked(1); return true; } private void handleRemovement(IFile resource) { XpandMarkerManager.deleteMarkers(resource); } /** * Should be called only for resources which are {@link XpandResourceVisitor#isFileOfInterest(IFile)} */ private void reloadResource(IFile resource) { assert resource.exists(); // TODO: remove this if unless we do compile other resources here // (QVTO) if (isXpand(resource)) { RootDescription rootDescription = getRootDescription(resource); Collection<IFile> resources = description2ResourcesMap.get(rootDescription); if (resources == null) { resources = new ArrayList<IFile>(); description2ResourcesMap.put(rootDescription, resources); } resources.add(resource); } } private boolean isFileOfInterest(IFile file) { if (!isXpand(file)) { return false; } if (getRootDescription(file) == null) { return false; } return true; } } }