/* Copyright (C) 2009 Mobile Sorcery AB This program is free software; you can redistribute it and/or modify it under the terms of the Eclipse Public License v1.0. 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 Eclipse Public License v1.0 for more details. You should have received a copy of the Eclipse Public License v1.0 along with this program. It is also available at http://www.eclipse.org/legal/epl-v10.html */ package com.mobilesorcery.sdk.internal.builder; import java.util.ArrayList; import java.util.HashSet; import java.util.List; import java.util.Set; import org.eclipse.core.resources.IProject; import org.eclipse.core.resources.IResource; import org.eclipse.core.resources.IResourceDelta; import org.eclipse.core.resources.IResourceVisitor; import org.eclipse.core.runtime.CoreException; import org.eclipse.core.runtime.IPath; import com.mobilesorcery.sdk.core.CoreMoSyncPlugin; import com.mobilesorcery.sdk.core.IBuildVariant; import com.mobilesorcery.sdk.core.IFileTreeDiff; import com.mobilesorcery.sdk.core.IFilter; import com.mobilesorcery.sdk.core.IProcessConsole; import com.mobilesorcery.sdk.core.IPropertyOwner; import com.mobilesorcery.sdk.core.MoSyncBuilder; import com.mobilesorcery.sdk.core.MoSyncProject; import com.mobilesorcery.sdk.core.ParameterResolver; import com.mobilesorcery.sdk.core.ParameterResolverException; import com.mobilesorcery.sdk.core.Util; import com.mobilesorcery.sdk.internal.dependencies.DependencyManager; import com.mobilesorcery.sdk.internal.dependencies.IDependencyProvider; /** * <p> * A base class for incremental builders. * </p> * <p> * It has support for aggregating changed, added, and deleted resources. * </p> * * @author Mattias Bybro * */ public abstract class IncrementalBuilderVisitor implements IResourceVisitor { protected List<IResource> changedOrAddedResources = new ArrayList<IResource>(); protected List<IResource> deletedResources = new ArrayList<IResource>(); protected IProject project; protected IProcessConsole console; private IFilter<IResource> resourceFilter; private IBuildVariant variant; private IDependencyProvider<IResource> dependencyProvider; private ParameterResolver resolver; @Override public boolean visit(IResource resource) throws CoreException { boolean isDerived = resource.isDerived(); if (!isDerived && doesAffectBuild(resource)) { changedOrAddedResources.add(resource); } return !isDerived; } public void addChangedOrAddedResources(IResource[] changedOrAddedResources) { for (int i = 0; i < changedOrAddedResources.length; i++) { IResource resource = changedOrAddedResources[i]; addChangedOrAddedResource(resource); } } public void addChangedOrAddedResource(IResource changedOrAddedResource) { if (doesAffectBuild(changedOrAddedResource)) { this.changedOrAddedResources.add(changedOrAddedResource); } } public void addDeletedResources(IResource[] deletedResources) { for (int i = 0; i < deletedResources.length; i++) { IResource resource = deletedResources[i]; addDeletedResource(resource); } } public void addDeletedResource(IResource deletedResource) { if (doesAffectBuild(deletedResource)) { this.deletedResources.add(deletedResource); } } public void addChangedOrAddedResources( IResourceDelta[] changedOrAddedResourceDeltas) { IResource[] changedOrAddedResources = new IResource[changedOrAddedResourceDeltas.length]; for (int i = 0; i < changedOrAddedResourceDeltas.length; i++) { changedOrAddedResources[i] = changedOrAddedResourceDeltas[i] .getResource(); addChangedOrAddedResources(changedOrAddedResourceDeltas[i] .getAffectedChildren(IResourceDelta.ADDED | IResourceDelta.CHANGED)); } addChangedOrAddedResources(changedOrAddedResources); } public IResource[] getChangedOrAddedResources() { return changedOrAddedResources.toArray(new IResource[0]); } public void addDeletedResources(IResourceDelta[] deletedResourceDeltas) { IResource[] deletedResources = new IResource[deletedResourceDeltas.length]; for (int i = 0; i < deletedResourceDeltas.length; i++) { deletedResources[i] = deletedResourceDeltas[i].getResource(); addDeletedResources(deletedResourceDeltas[i] .getAffectedChildren(IResourceDelta.REMOVED)); } addDeletedResources(deletedResources); } public IResource[] getDeletedResources() { return deletedResources.toArray(new IResource[0]); } public IResource[] getAllAffectedResources() { ArrayList<IResource> result = new ArrayList<IResource>(deletedResources); result.addAll(changedOrAddedResources); return result.toArray(new IResource[result.size()]); } public List<IResource> computeChangeSet() { ArrayList<IResource> changeSet = new ArrayList<IResource>(); changeSet.addAll(deletedResources); changeSet.addAll(changedOrAddedResources); return changeSet; } public void setProject(IProject project) { this.project = project; } public IProject getProject() { return project; } public void setConsole(IProcessConsole console) { this.console = console; } /** * Sets the resources delta, which is used to produce change sets. * * @param delta * May be null, which indicates a full build and in which case a * full visit is performed * @throws CoreException */ public void setDiff(IFileTreeDiff diff) throws CoreException { changedOrAddedResources.clear(); deletedResources.clear(); if (diff == null) { project.accept(this); } else { for (IPath added : diff.getAdded()) { addChangedOrAddedResource(project.findMember(added)); } for (IPath changed : diff.getChanged()) { addChangedOrAddedResource(project.findMember(changed)); } for (IPath removed : diff.getRemoved()) { addDeletedResource(project.getFile(removed)); } } } /** * Checks whether a resource affects this visitor's build process; if not, * then it will not show up as a changed, added or deleted resource. * * @param resource * @return */ public boolean doesAffectBuild(IResource resource) { if (resource == null || resource.isDerived() || MoSyncBuilder.isInOutput(resource.getProject(), resource)) { return false; } // TODO: Make this much faster! MoSyncProject project = MoSyncProject.create(this.project); IFilter<IResource> resourceFilter = this.resourceFilter; if (resourceFilter == null) { resourceFilter = MoSyncProject.getExclusionFilter(project, true); } return project == null || resourceFilter.accept(resource); } /** * <p>Checks whether a resource is an actual part of the build; or actually * passed into a tool/compiler of some kind. * The difference between this and <code>doesAffectBuild</code> * is that only resources that get a <code>true</code> from * this method will be included in the results returned from * <code>computeResourcesToRebuild()</code>.</p> * <p>The default implementation just passes it to * doesAffectBuild.</p> * @param resource * @return */ public boolean isBuildable(IResource resource) { return doesAffectBuild(resource); } public Set<IResource> computeResourcesToRebuild( DependencyManager<IResource> dependencies) { Set<IResource> recompileThese = new HashSet<IResource>( changedOrAddedResources); List<IResource> changeSet = computeChangeSet(); if (dependencies != null) { Set<IResource> reverseDependencies = dependencies .getReverseDependenciesOf(changeSet, DependencyManager.DEPTH_INFINITE); if (CoreMoSyncPlugin.getDefault().isDebugging()) { CoreMoSyncPlugin.trace("Change set: " + changeSet); CoreMoSyncPlugin.trace("Trigger recompile of: " + reverseDependencies); } recompileThese.addAll(reverseDependencies); recompileThese.removeAll(deletedResources); } Set<IResource> result = new HashSet<IResource>(); for (IResource recompileThis : recompileThese) { if (isBuildable(recompileThis)) { result.add(recompileThis); } } return result; } public void setVariant(IBuildVariant variant) { this.variant = variant; } protected IBuildVariant getVariant() { return variant; } public IPropertyOwner getBuildProperties() { return MoSyncBuilder.getPropertyOwner(MoSyncProject.create(project), variant.getConfigurationId()); } public void setResourceFilter(IFilter<IResource> resourceFilter) { this.resourceFilter = resourceFilter; } public void setDependencyProvider(IDependencyProvider<IResource> dependencyProvider) { this.dependencyProvider = dependencyProvider; } public IDependencyProvider<IResource> getDependencyProvider() { return dependencyProvider; } public void setParameterResolver(ParameterResolver resolver) { this.resolver = resolver; } protected ParameterResolver getParameterResolver() { return resolver; } protected void resolvePaths(IPath[] paths) throws ParameterResolverException { MoSyncBuilder.resolvePaths(paths, resolver); } protected String resolve(String value) throws ParameterResolverException { return Util.replace(value, resolver); } }