/*
* Copyright (c) 2013 Data Harmonisation Panel
*
* All rights reserved. This program and the accompanying materials are made
* available under the terms of the GNU Lesser General Public License as
* published by the Free Software Foundation, either version 3 of the License,
* or (at your option) any later version.
*
* You should have received a copy of the GNU Lesser General Public License
* along with this distribution. If not, see <http://www.gnu.org/licenses/>.
*
* Contributors:
* Data Harmonisation Panel <http://www.dhpanel.eu>
*/
package eu.esdihumboldt.hale.ui.util.source;
import java.util.HashSet;
import java.util.Set;
import java.util.concurrent.locks.ReentrantLock;
import org.eclipse.core.runtime.IProgressMonitor;
import org.eclipse.core.runtime.IStatus;
import org.eclipse.core.runtime.Status;
import org.eclipse.core.runtime.jobs.Job;
import org.eclipse.jface.text.IDocument;
import org.eclipse.jface.text.source.IOverviewRuler;
import org.eclipse.jface.text.source.IVerticalRuler;
import org.eclipse.jface.util.PropertyChangeEvent;
import org.eclipse.swt.widgets.Composite;
import com.google.common.util.concurrent.ListenableFuture;
import com.google.common.util.concurrent.SettableFuture;
import de.fhg.igd.slf4jplus.ALogger;
import de.fhg.igd.slf4jplus.ALoggerFactory;
import eu.esdihumboldt.hale.ui.util.jobs.ExclusiveSchedulingRule;
/**
* Source viewer that independent of validation additionally does a compilation
* of the content, e.g. for use with content assist.
*
* @param <C> the type of the compilation result
*
* @author Simon Templer
*/
public class CompilingSourceViewer<C> extends ValidatingSourceViewer {
private static final ALogger log = ALoggerFactory.getLogger(CompilingSourceViewer.class);
/**
* Name of the property holding the compilation result.
*/
public static final String PROPERTY_COMPILED = "compiled";
/**
* The compile job.
*/
private Job compileJob;
/**
* Compilation scheduling delay in milliseconds.
*/
private static final int COMPILE_DELAY = 100;
private final boolean compilationEnabled;
private final ReentrantLock changeLock = new ReentrantLock();
/**
* If the document has changed since compilation. Protected by lock.
*/
private boolean changed = true;
/**
* Set of futures to update. Protected by lock.
*/
private final Set<SettableFuture<C>> toUpdate = new HashSet<>();
/**
* The compilation result. Protected by lock.
*/
private C compiled;
private final SourceCompiler<C> compiler;
/**
* Constructs a new validating source viewer.
*
* @param parent the parent of the viewer's control
* @param ruler the vertical ruler used by this source viewer
* @param styles the SWT style bits for the viewer's control
* @param validator the source validator
* @param compiler the source compiler, <code>null</code> to disable
* compilation
*/
public CompilingSourceViewer(Composite parent, IVerticalRuler ruler, int styles,
SourceValidator validator, SourceCompiler<C> compiler) {
super(parent, ruler, styles, validator);
this.compiler = compiler;
this.compilationEnabled = compiler != null;
}
/**
* Constructs a new validating source viewer.
*
* @param parent the parent of the viewer's control
* @param verticalRuler the vertical ruler used by this source viewer
* @param overviewRuler the overview ruler
* @param showAnnotationsOverview <code>true</code> if the overview ruler
* should be visible, <code>false</code> otherwise
* @param styles the SWT style bits for the viewer's control
* @param validator the source validator
* @param compiler the source compiler, <code>null</code> to disable
* compilation
*/
public CompilingSourceViewer(Composite parent, IVerticalRuler verticalRuler,
IOverviewRuler overviewRuler, boolean showAnnotationsOverview, int styles,
SourceValidator validator, SourceCompiler<C> compiler) {
super(parent, verticalRuler, overviewRuler, showAnnotationsOverview, styles, validator);
this.compiler = compiler;
this.compilationEnabled = compiler != null;
}
/**
* Get the compilation result.
*
* @return a future reference to the compilation result, the actual
* compilation result may be <code>null</code> if the compilation
* failed
*/
public ListenableFuture<C> getCompiled() {
SettableFuture<C> result = SettableFuture.create();
if (!compilationEnabled) {
result.set(null);
return result;
}
changeLock.lock();
try {
if (changed) {
// compilation result is not up-to-date
toUpdate.add(result);
}
else {
result.set(compiled);
}
} finally {
changeLock.unlock();
}
return result;
}
@Override
protected void init() {
compileJob = new Job("Compile") {
@Override
public boolean shouldRun() {
return compilationEnabled;
}
@Override
public boolean shouldSchedule() {
return compilationEnabled;
}
@Override
protected IStatus run(IProgressMonitor monitor) {
String content;
changeLock.lock();
try {
if (!changed) {
return Status.OK_STATUS;
}
IDocument doc = getDocument();
if (doc != null) {
content = doc.get();
}
else {
content = null;
}
changed = false;
} finally {
changeLock.unlock();
}
C result = null;
if (content != null) {
try {
// this is the potentially long running stuff
result = compile(content);
} catch (Exception e) {
// ignore, but log
log.warn("Error compiling document content", e);
}
}
boolean notify = false;
C previous = null;
changeLock.lock();
try {
/*
* Only notify listeners if the document was not changed in
* the meantime.
*/
notify = !changed;
if (notify) {
// set result
previous = compiled;
compiled = result;
// set result for futures
for (SettableFuture<C> future : toUpdate) {
future.set(result);
}
toUpdate.clear();
}
} finally {
changeLock.unlock();
}
if (notify) {
// notify listeners
PropertyChangeEvent event = new PropertyChangeEvent(CompilingSourceViewer.this,
PROPERTY_COMPILED, previous, result);
notifyOnPropertyChange(event);
}
return Status.OK_STATUS;
}
};
compileJob.setSystem(true);
compileJob.setRule(new ExclusiveSchedulingRule(compileJob));
super.init();
}
/**
* Do the compilation.
*
* @param content the document content
* @return the compilation result
*/
protected final C compile(String content) {
return compiler.compile(content);
}
@Override
protected void scheduleValidation() {
if (compilationEnabled) {
changeLock.lock();
try {
changed = true;
} finally {
changeLock.unlock();
}
// schedule compilation
compileJob.schedule(COMPILE_DELAY);
}
super.scheduleValidation();
}
}