/*
* ====================================================================
*
* The ObjectStyle Group Software License, Version 1.0
*
* Copyright (c) 2005 - 2006 The ObjectStyle Group and individual authors of the
* software. All rights reserved.
*
* Redistribution and use in source and binary forms, with or without
* modification, are permitted provided that the following conditions are met: 1.
* Redistributions of source code must retain the above copyright notice, this
* list of conditions and the following disclaimer. 2. Redistributions in
* binary form must reproduce the above copyright notice, this list of
* conditions and the following disclaimer in the documentation and/or other
* materials provided with the distribution. 3. The end-user documentation
* included with the redistribution, if any, must include the following
* acknowlegement: "This product includes software developed by the ObjectStyle
* Group (http://objectstyle.org/)." Alternately, this acknowlegement may
* appear in the software itself, if and wherever such third-party
* acknowlegements normally appear. 4. The names "ObjectStyle Group" and
* "Cayenne" must not be used to endorse or promote products derived from this
* software without prior written permission. For written permission, please
* contact andrus@objectstyle.org. 5. Products derived from this software may
* not be called "ObjectStyle" nor may "ObjectStyle" appear in their names
* without prior written permission of the ObjectStyle Group.
*
* THIS SOFTWARE IS PROVIDED ``AS IS'' AND ANY EXPRESSED OR IMPLIED WARRANTIES,
* INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND
* FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE
* OBJECTSTYLE GROUP OR ITS CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT,
* INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
* LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA,
* OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF
* LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING
* NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE,
* EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
* ====================================================================
*
* This software consists of voluntary contributions made by many individuals
* on behalf of the ObjectStyle Group. For more information on the ObjectStyle
* Group, please see <http://objectstyle.org/> .
*
*/
package org.objectstyle.wolips.wodclipse.core.builder;
import java.util.HashSet;
import java.util.Iterator;
import java.util.Map;
import java.util.Set;
import java.util.concurrent.ConcurrentLinkedQueue;
import java.util.concurrent.ExecutorService;
import java.util.concurrent.Executors;
import java.util.concurrent.ThreadPoolExecutor;
import org.eclipse.core.resources.IContainer;
import org.eclipse.core.resources.IFile;
import org.eclipse.core.resources.IProject;
import org.eclipse.core.resources.IResource;
import org.eclipse.core.resources.IncrementalProjectBuilder;
import org.eclipse.core.runtime.IProgressMonitor;
import org.eclipse.core.runtime.IStatus;
import org.eclipse.core.runtime.Path;
import org.eclipse.core.runtime.Status;
import org.eclipse.core.runtime.jobs.Job;
import org.eclipse.jdt.core.ICompilationUnit;
import org.eclipse.jdt.core.IType;
import org.eclipse.jdt.core.ITypeHierarchy;
import org.eclipse.jdt.core.JavaCore;
import org.eclipse.jface.preference.IPreferenceStore;
import org.objectstyle.wolips.bindings.Activator;
import org.objectstyle.wolips.bindings.preferences.PreferenceConstants;
import org.objectstyle.wolips.core.resources.builder.AbstractFullAndIncrementalBuilder;
import org.objectstyle.wolips.core.resources.types.SuperTypeHierarchyCache;
import org.objectstyle.wolips.core.resources.types.WOHierarchyScope;
import org.objectstyle.wolips.locate.LocatePlugin;
import org.objectstyle.wolips.locate.result.LocalizedComponentsLocateResult;
import org.objectstyle.wolips.variables.BuildProperties;
import org.objectstyle.wolips.variables.VariablesPlugin;
import org.objectstyle.wolips.wodclipse.core.completion.WodParserCache;
import org.objectstyle.wolips.wodclipse.core.util.WodModelUtils;
public class WodBuilder extends AbstractFullAndIncrementalBuilder {
private static ExecutorService validationThreadPool;
private static ValidationProgressJob _validationJob;
private boolean _validateTemplatesAtAll;
private boolean _validateTemplatesNow;
private int _buildKind;
private boolean _threadedBuild;
static {
WodBuilder._validationJob = new ValidationProgressJob();
WodBuilder.validationThreadPool = Executors.newFixedThreadPool(Runtime.getRuntime().availableProcessors() * 2);
}
public WodBuilder() {
super();
}
public boolean isEnabled() {
return true;
}
// MS: This should probably move up to a higher level utility method
protected static boolean getBooleanProperty(String propertiesKey, String preferencesKey, IProject project, IPreferenceStore preferenceStore) {
boolean value;
BuildProperties buildProperties = (BuildProperties) project.getAdapter(BuildProperties.class);
String buildPropertiesValueStr = buildProperties.get(propertiesKey);
if (buildPropertiesValueStr != null) {
value = "true".equalsIgnoreCase(buildPropertiesValueStr);
}
else {
String globalPropertiesValueStr = VariablesPlugin.getDefault().getGlobalVariables().getString(propertiesKey + "." + project.getName());
if (globalPropertiesValueStr != null) {
value = "true".equalsIgnoreCase(globalPropertiesValueStr);
}
else {
value = preferenceStore.getBoolean(preferencesKey);
}
}
return value;
}
public boolean buildStarted(int kind, Map args, IProgressMonitor monitor, IProject project, Map buildCache) {
_buildKind = kind;
if (getBooleanProperty("component.validateTemplates", PreferenceConstants.VALIDATE_TEMPLATES_KEY, project, Activator.getDefault().getPreferenceStore())) {
_validateTemplatesAtAll = true;
if (!getBooleanProperty("component.validateTemplatesOnBuild", PreferenceConstants.VALIDATE_TEMPLATES_ON_BUILD_KEY, project, Activator.getDefault().getPreferenceStore())) {
_validateTemplatesNow = false;
}
else {
_validateTemplatesNow = true;
}
}
_threadedBuild = getBooleanProperty("component.threadedValidation", PreferenceConstants.THREADED_VALIDATION_KEY, project, Activator.getDefault().getPreferenceStore());
if (kind == IncrementalProjectBuilder.FULL_BUILD) {
WodParserCache.getModelGroupCache().clearCacheForProject(project);
WodParserCache.getTypeCache().clearCacheForProject(project);
WOHierarchyScope.clearCacheForProject(project);
}
return false;
}
public boolean buildPreparationDone(int kind, Map args, IProgressMonitor monitor, IProject project, Map buildCache) {
return false;
}
public void handleClasses(IResource resource, IProgressMonitor monitor, Map buildCache) {
// DO NOTHING
}
public void handleSource(IResource resource, IProgressMonitor progressMonitor, Map buildCache) {
if (_validateTemplatesNow) {
try {
if (_buildKind == IncrementalProjectBuilder.INCREMENTAL_BUILD || _buildKind == IncrementalProjectBuilder.AUTO_BUILD) {
ICompilationUnit compilationUnit = JavaCore.createCompilationUnitFrom((IFile) resource);
if (compilationUnit != null) {
IType type = compilationUnit.findPrimaryType();
if (type != null) {
IType woElementType = type.getJavaProject().findType("com.webobjects.appserver.WOElement", progressMonitor);
if (woElementType != null) {
ITypeHierarchy typeHierarchy = SuperTypeHierarchyCache.getTypeHierarchy(type, progressMonitor);
if (typeHierarchy != null && typeHierarchy.contains(woElementType)) {
LocalizedComponentsLocateResult results = LocatePlugin.getDefault().getLocalizedComponentsLocateResult(resource);
IFile wodFile = results.getFirstWodFile();
if (wodFile != null && wodFile.exists()) {
wodFile.touch(progressMonitor);
validateWodFile(wodFile, progressMonitor);
}
}
}
}
}
}
// touchRelatedResources(_resource, _progressMonitor, _buildCache);
}
catch (Throwable t) {
Activator.getDefault().log(t);
}
}
}
public void handleClasspath(IResource resource, IProgressMonitor monitor, Map buildCache) {
// DO NOTHING
}
@SuppressWarnings("unchecked")
protected Set<IContainer> componentBuildCache(Map buildCache) {
Set<IContainer> builtComponents = (Set<IContainer>) buildCache.get("builtComponents");
if (builtComponents == null) {
builtComponents = new HashSet<IContainer>();
buildCache.put("builtComponents", builtComponents);
}
return builtComponents;
}
public void handleOther(IResource resource, IProgressMonitor monitor, Map buildCache) {
// ignore
}
protected boolean shouldValidate(IResource resource, Map buildCache) {
boolean validate = false;
Set<IContainer> builtComponents = componentBuildCache(buildCache);
IContainer woFolder;
if (resource instanceof IFile) {
woFolder = resource.getParent();
}
else if (resource instanceof IContainer) {
woFolder = (IContainer) resource;
}
else {
woFolder = null;
}
if (woFolder != null && !builtComponents.contains(woFolder)) {
validate = true;
builtComponents.add(woFolder);
}
return validate;
}
public void handleWebServerResources(IResource resource, IProgressMonitor monitor, Map buildCache) {
// DO NOTHING
}
public void handleWoappResources(IResource resource, IProgressMonitor monitor, Map buildCache) {
if (_validateTemplatesNow) {
try {
boolean validate = false;
if (resource instanceof IFile) {
if (resource.getParent().getName().endsWith(".wo")) {
IFile file = (IFile) resource;
String fileExtension = file.getFileExtension();
if ("wod".equals(fileExtension)) {
validate = shouldValidate(file, buildCache);
}
else if ("html".equals(fileExtension)) {
validate = shouldValidate(file, buildCache);
}
else if ("api".equals(fileExtension)) {
// should we really do something with the component when
// we change the api?
// shoulnd't we validate all files using the api?
validate = false;
}
else if ("woo".equals(fileExtension)) {
validate = shouldValidate(file, buildCache);
}
}
}
else if (resource instanceof IContainer) {
IContainer folder = (IContainer) resource;
String fileExtension = folder.getFileExtension();
if ("wo".equals(fileExtension)) {
validate = shouldValidate(folder, buildCache);
}
}
if (validate) {
validateWodFile(resource, monitor);
}
}
catch (Throwable e) {
Activator.getDefault().log(e);
}
}
else if (!_validateTemplatesAtAll || (_buildKind == IncrementalProjectBuilder.FULL_BUILD || _buildKind == IncrementalProjectBuilder.CLEAN_BUILD)) {
if (resource instanceof IFile) {
IFile file = (IFile) resource;
String fileExtension = file.getFileExtension();
if ("wod".equals(fileExtension)) {
WodModelUtils.deleteProblems(file);
}
else if ("html".equals(fileExtension)) {
WodModelUtils.deleteProblems(file);
}
else if ("woo".equals(fileExtension)) {
WodModelUtils.deleteProblems(file);
}
}
else if (resource instanceof IContainer) {
IContainer folder = (IContainer) resource;
String fileExtension = folder.getFileExtension();
if ("wo".equals(fileExtension)) {
String componentName = folder.getName().substring(0, folder.getName().lastIndexOf('.'));
WodModelUtils.deleteProblems(folder.getFile(new Path(componentName + ".html")));
WodModelUtils.deleteProblems(folder.getFile(new Path(componentName + ".wod")));
WodModelUtils.deleteProblems(folder.getFile(new Path(componentName + ".woo")));
}
}
}
}
protected void validateWodFile(IResource resource, IProgressMonitor progressMonitor) {
// System.out.println("WodBuilder.validateWodFile: " + resource);
WodBuilder.validateComponent(resource, _threadedBuild, progressMonitor);
}
public static void validateComponent(IResource resource, boolean threaded, IProgressMonitor progressMonitor) {
if (threaded) {
WodBuilder.validationThreadPool.execute(new ValidatingComponent(resource, progressMonitor));
}
else {
WodBuilder._validateComponent(resource, progressMonitor, true);
}
}
public static void _validateComponent(IResource resource, IProgressMonitor progressMonitor, boolean showProgress) {
if (resource != null) {
// System.out.println("WodBuilder._validateComponent: " + resource);
String resourceName = resource.getName();
if (progressMonitor != null) {
if (showProgress) {
progressMonitor.subTask("Locating components for " + resourceName + " ...");
}
}
try {
WodParserCache cache = WodParserCache.parser(resource);
if (progressMonitor != null && cache.getWodEntry().getFile() != null) {
if (showProgress) {
progressMonitor.subTask("Building WO " + cache.getWodEntry().getFile().getName() + " ...");
}
}
cache.clearParserCache();
cache.parse();
cache.validate(true, false);
}
catch (Throwable t) {
t.printStackTrace();
}
// System.out.println("WodBuilder._validateComponent: done with " +
// resource);
}
}
public static class ValidationProgressJob extends Job {
private ConcurrentLinkedQueue<IResource> _validatingResources;
private Object _lock = new Object();
public ValidationProgressJob() {
super("Component Validation");
setPriority(Job.LONG);
setUser(false);
// setProperty(IProgressConstants.ICON_PROPERTY, getImage());
_validatingResources = new ConcurrentLinkedQueue<IResource>();
}
public void start(IResource resource) {
_validatingResources.add(resource);
WOHierarchyScope.incrementReferenceCountForProject(resource.getProject());
synchronized (_lock) {
_lock.notifyAll();
}
schedule();
}
public void finish(IResource resource) {
_validatingResources.remove(resource);
WOHierarchyScope.decrementReferenceCountForProject(resource.getProject());
synchronized (_lock) {
_lock.notifyAll();
}
}
@Override
protected IStatus run(IProgressMonitor monitor) {
IStatus status = null;
long completedAtStart = ((ThreadPoolExecutor) WodBuilder.validationThreadPool).getCompletedTaskCount();
while (!monitor.isCanceled() && status == null) {
synchronized (_lock) {
if (_validatingResources.size() == 0) {
monitor.done();
status = Status.OK_STATUS;
}
else {
setName("Component Validation");
try {
_lock.wait();
}
catch (InterruptedException e) {
e.printStackTrace();
}
}
}
if (_validatingResources.size() > 0 && !monitor.isCanceled()) {
monitor.beginTask("Validating ...", IProgressMonitor.UNKNOWN);
StringBuffer sb = new StringBuffer();
sb.append("Validating ");
Iterator<IResource> resourceIter = _validatingResources.iterator();
while (resourceIter.hasNext()) {
IResource resource = resourceIter.next();
sb.append(resource.getName());
if (resourceIter.hasNext()) {
sb.append(", ");
}
}
long taskCount = ((ThreadPoolExecutor) WodBuilder.validationThreadPool).getTaskCount() - completedAtStart;
long completedTaskCount = ((ThreadPoolExecutor) WodBuilder.validationThreadPool).getCompletedTaskCount() - completedAtStart;
sb.append(" (" + completedTaskCount + " of " + taskCount + ")");
sb.append("...");
monitor.setTaskName(sb.toString());
monitor.beginTask(sb.toString(), (int) taskCount);
monitor.worked((int) completedTaskCount);
}
}
return monitor.isCanceled() ? Status.CANCEL_STATUS : status;
}
}
public static class ValidatingComponent implements Runnable {
private IResource _resource;
private IProgressMonitor _monitor;
public ValidatingComponent(IResource resource, IProgressMonitor monitor) {
_resource = resource;
_monitor = monitor;
}
public void run() {
if (_monitor == null || !_monitor.isCanceled()) {
_validationJob.start(_resource);
try {
WodBuilder._validateComponent(_resource, _monitor, false);
}
finally {
_validationJob.finish(_resource);
}
}
else {
// System.out.println("BuildingComponent.run: cancelled " + _resource);
}
}
}
}