/*******************************************************************************
* Copyright (c) 2011, 2012 Red Hat, Inc.
* All rights reserved.
* This program is 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:
* Red Hat, Inc. - initial API and implementation
*******************************************************************************/
package org.eclipse.bpmn2.modeler.core.builder;
import java.io.FileInputStream;
import java.util.Hashtable;
import java.util.Map;
import java.util.Stack;
import javax.xml.parsers.ParserConfigurationException;
import javax.xml.parsers.SAXParser;
import javax.xml.parsers.SAXParserFactory;
import org.eclipse.bpmn2.modeler.core.preferences.Bpmn2Preferences;
import org.eclipse.bpmn2.modeler.core.runtime.TargetRuntime;
import org.eclipse.bpmn2.modeler.core.runtime.XMLConfigElement;
import org.eclipse.bpmn2.modeler.core.utils.ErrorDialog;
import org.eclipse.bpmn2.modeler.core.validation.BPMN2ProjectValidator;
import org.eclipse.core.resources.IContainer;
import org.eclipse.core.resources.IFile;
import org.eclipse.core.resources.IFolder;
import org.eclipse.core.resources.IMarker;
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.IConfigurationElement;
import org.eclipse.core.runtime.IProgressMonitor;
import org.xml.sax.Attributes;
import org.xml.sax.SAXException;
import org.xml.sax.SAXParseException;
import org.xml.sax.helpers.DefaultHandler;
public class BPMN2Builder extends IncrementalProjectBuilder {
public static final String BUILDER_ID = "org.eclipse.bpmn2.modeler.core.bpmn2Builder"; //$NON-NLS-1$
private static final String MARKER_TYPE = "org.eclipse.bpmn2.modeler.core.xmlProblem"; //$NON-NLS-1$
public static final String CONFIG_FOLDER = ".bpmn2config"; //$NON-NLS-1$
private SAXParserFactory parserFactory;
private Hashtable<IFolder, Long> timestamps = new Hashtable<IFolder, Long>();
public static final BPMN2Builder INSTANCE = new BPMN2Builder();
class BPMN2DeltaVisitor implements IResourceDeltaVisitor {
IProgressMonitor monitor;
public BPMN2DeltaVisitor(IProgressMonitor monitor) {
this.monitor = monitor;
}
/*
* (non-Javadoc)
*
* @see
* org.eclipse.core.resources.IResourceDeltaVisitor#visit(org.eclipse
* .core.resources.IResourceDelta)
*/
public boolean visit(IResourceDelta delta) throws CoreException {
IResource resource = delta.getResource();
if (resource instanceof IFile) {
IContainer container = resource.getParent();
if (CONFIG_FOLDER.equals(container.getName()) && container.getParent() instanceof IProject) {
int kind = delta.getKind();
if (kind==IResourceDelta.REMOVED) {
unloadExtension((IFile) resource);
}
else {
loadExtension((IFile) resource);
}
return true;
}
}
switch (delta.getKind()) {
case IResourceDelta.ADDED:
// handle added resource
// checkXML(resource);
validate(delta, monitor);
break;
case IResourceDelta.REMOVED:
// handle removed resource
break;
case IResourceDelta.CHANGED:
// handle changed resource
// checkXML(resource);
validate(delta, monitor);
break;
}
// return true to continue visiting children.
return true;
}
}
class BPMN2ResourceVisitor implements IResourceVisitor {
IProgressMonitor monitor;
public BPMN2ResourceVisitor(IProgressMonitor monitor) {
this.monitor = monitor;
}
public boolean visit(IResource resource) {
// checkXML(resource);
validate(resource, monitor);
// return true to continue visiting children.
return true;
}
}
/*
* (non-Javadoc)
*
* @see org.eclipse.core.internal.events.InternalBuilder#build(int,
* java.util.Map, org.eclipse.core.runtime.IProgressMonitor)
*/
protected IProject[] build(int kind, Map args, IProgressMonitor monitor) throws CoreException {
if (kind == FULL_BUILD) {
fullBuild(monitor);
} else {
IResourceDelta delta = getDelta(getProject());
if (delta == null) {
fullBuild(monitor);
} else {
incrementalBuild(delta, monitor);
}
}
return null;
}
protected void incrementalBuild(IResourceDelta delta, IProgressMonitor monitor) throws CoreException {
// the visitor does the work.
delta.accept(new BPMN2DeltaVisitor(monitor));
}
void validate(IResourceDelta delta, IProgressMonitor monitor) {
// This project builder should not be doing validation.
// Validation is being handled by the Eclipse Validation Builder
// and can be enabled/disabled from the User Preferences -> Validation page.
// BPMN2ProjectValidator.validate(delta, monitor);
}
void validate(IResource resource, IProgressMonitor monitor) {
// BPMN2ProjectValidator.validate(resource, monitor);
}
protected void fullBuild(final IProgressMonitor monitor) throws CoreException {
try {
getProject().accept(new BPMN2ResourceVisitor(monitor));
} catch (CoreException e) {
}
}
/**
* Load all extension definition files contained in the given IProject's CONFIG_FOLDER folder.
* The timestamp of this folder is cached so that it only gets loaded once.
* Individual extension definition files are loaded by the builder if/when they are modified.
*
* @param project
*/
public void loadExtensions(IProject project) {
try {
IFolder folder = project.getFolder(CONFIG_FOLDER);
if (folder.exists()) {
Long timestamp = timestamps.get(folder);
if (timestamp==null || timestamp.longValue() < folder.getLocalTimeStamp()) {
timestamps.put(folder, new Long(folder.getLocalTimeStamp()));
for (IResource r : folder.members()) {
if (r instanceof IFile && r.exists()) {
loadExtension((IFile) r);
}
}
}
}
} catch (Exception e) {
e.printStackTrace();
}
}
public void loadExtension(IFile file) {
XMLConfigElementHandler handler = new XMLConfigElementHandler(file);
try {
TargetRuntime rt = Bpmn2Preferences.getInstance(file.getProject()).getRuntime();
if (file.exists() && file.getLocalTimeStamp() > rt.getConfigFileTimestamp()) {
SAXParser parser = getParser();
FileInputStream fis = new FileInputStream(file.getLocation().makeAbsolute().toOSString());
parser.parse(fis, handler);
IConfigurationElement element = handler.root.getChildren()[0];
TargetRuntime.loadExtensions(rt, element.getChildren(), file);
}
} catch (Exception e) {
ErrorDialog dlg = new ErrorDialog(Messages.BPMN2Builder_ConfigFileError_Title, e);
dlg.show();
}
}
public void unloadExtension(IFile file) {
TargetRuntime.unloadExtensions(file);
}
private class XMLConfigElementHandler extends XMLErrorHandler {
public XMLConfigElement root;
private Stack<XMLConfigElement> stack = new Stack<XMLConfigElement>();
public XMLConfigElementHandler(IFile file) {
super(file);
}
@Override
public void characters(char[] ch, int start, int length) throws SAXException {
String value = new String(ch, start, length).trim();
if (!value.isEmpty()) {
stack.peek().setValue(value);
}
}
@Override
public void endDocument() throws SAXException {
stack.pop();
}
@Override
public void endElement(String arg0, String arg1, String arg2) throws SAXException {
stack.pop();
}
@Override
public void startDocument() throws SAXException {
root = new XMLConfigElement(file.getProject());
stack.push(root);
}
@Override
public void startElement(String uri, String localName, String qName, Attributes attributes) throws SAXException {
XMLConfigElement element = new XMLConfigElement(stack.peek(), qName);
for (int i = 0; i < attributes.getLength(); ++i) {
element.setAttribute(attributes.getQName(i), attributes.getValue(i));
}
stack.push(element);
}
}
private class XMLErrorHandler extends DefaultHandler {
protected IFile file;
public XMLErrorHandler(IFile file) {
this.file = file;
}
private void addMarker(SAXParseException e, int severity) {
BPMN2Builder.this.addMarker(file, e.getMessage(), e.getLineNumber(), severity);
}
public void error(SAXParseException exception) throws SAXException {
addMarker(exception, IMarker.SEVERITY_ERROR);
}
public void fatalError(SAXParseException exception) throws SAXException {
addMarker(exception, IMarker.SEVERITY_ERROR);
}
public void warning(SAXParseException exception) throws SAXException {
addMarker(exception, IMarker.SEVERITY_WARNING);
}
}
/**
* @param file
* @param message
* @param lineNumber
* @param severity
*/
private void addMarker(IFile file, String message, int lineNumber, int severity) {
try {
IMarker marker = file.createMarker(MARKER_TYPE);
marker.setAttribute(IMarker.MESSAGE, message);
marker.setAttribute(IMarker.SEVERITY, severity);
if (lineNumber == -1) {
lineNumber = 1;
}
marker.setAttribute(IMarker.LINE_NUMBER, lineNumber);
} catch (CoreException e) {
}
}
/**
* @param resource
*/
void checkXML(IResource resource) {
if (BPMN2ProjectValidator.isBPMN2File(resource)) {
IFile file = (IFile) resource;
deleteMarkers(file);
XMLErrorHandler reporter = new XMLErrorHandler(file);
try {
getParser().parse(file.getContents(), reporter);
} catch (Exception e1) {
}
}
}
/**
* @param file
*/
private void deleteMarkers(IFile file) {
try {
file.deleteMarkers(MARKER_TYPE, false, IResource.DEPTH_ZERO);
} catch (CoreException ce) {
}
}
/**
* @return
* @throws ParserConfigurationException
* @throws SAXException
*/
private SAXParser getParser() throws ParserConfigurationException, SAXException {
if (parserFactory == null) {
parserFactory = SAXParserFactory.newInstance();
}
return parserFactory.newSAXParser();
}
}