package org.jbehave.eclipse;
import static org.jbehave.eclipse.util.Objects.o;
import java.util.Locale;
import java.util.concurrent.CopyOnWriteArrayList;
import java.util.concurrent.Executor;
import org.eclipse.core.resources.IProject;
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.core.runtime.preferences.IEclipsePreferences.IPreferenceChangeListener;
import org.eclipse.core.runtime.preferences.IEclipsePreferences.PreferenceChangeEvent;
import org.eclipse.jdt.core.IJavaElementDelta;
import org.eclipse.jdt.core.IJavaProject;
import org.eclipse.jdt.core.IMethod;
import org.eclipse.jdt.core.JavaCore;
import org.eclipse.jdt.core.JavaModelException;
import org.jbehave.core.steps.StepType;
import org.jbehave.eclipse.cache.JavaScanner;
import org.jbehave.eclipse.cache.MethodCache;
import org.jbehave.eclipse.cache.MethodCache.Callback;
import org.jbehave.eclipse.cache.StepCandidateCacheListener;
import org.jbehave.eclipse.cache.StepCandidateCacheLoader;
import org.jbehave.eclipse.cache.container.Container;
import org.jbehave.eclipse.editor.step.LocalizedStepSupport;
import org.jbehave.eclipse.editor.step.MethodToStepCandidateReducer;
import org.jbehave.eclipse.editor.step.StepCandidate;
import org.jbehave.eclipse.editor.step.StepCandidateReduceListener;
import org.jbehave.eclipse.editor.step.StepLocator;
import org.jbehave.eclipse.preferences.ClassScannerPreferences;
import org.jbehave.eclipse.preferences.ProjectPreferences;
import org.jbehave.eclipse.util.LocaleUtils;
import org.jbehave.eclipse.util.New;
import org.jbehave.eclipse.util.Visitor;
import org.osgi.service.prefs.BackingStoreException;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import fj.Effect;
public class JBehaveProject implements StepCandidateCacheListener {
private static Logger log = LoggerFactory.getLogger(JBehaveProject.class);
private IProject project;
private MethodCache<StepCandidate> cache;
private StepCandidateCacheLoader cacheLoader;
private LocalizedStepSupport localizedStepSupport;
private ProjectPreferences projectPreferences;
private ClassScannerPreferences classScannerPreferences;
//
private CopyOnWriteArrayList<JBehaveProjectListener> listeners = New.copyOnWriteArrayList();
private String parameterPrefix;
public JBehaveProject(IProject project) {
this.project = project;
this.cacheLoader = new StepCandidateCacheLoader(this,
Activator.getDefault().getExecutor(),
JBehaveProject.getSystemJobAsExecutor("JBehave cache refresh task"));
this.localizedStepSupport = new LocalizedStepSupport();
initializeProjectPreferencesAndListener(project);
initializeClassScannerPreferencesAndListener(project);
}
private MethodCache<StepCandidate> createMethodCache() {
return new MethodCache<StepCandidate>(newCallback());
}
protected void initializeClassScannerPreferencesAndListener(IProject project) {
this.classScannerPreferences = new ClassScannerPreferences(project);
this.classScannerPreferences.addListener(new IPreferenceChangeListener() {
public void preferenceChange(PreferenceChangeEvent changeEvent) {
log.info("Class scanner preference changed [{}]: <{}> -> <{}>",
o(changeEvent.getKey(), changeEvent.getOldValue(), changeEvent.getNewValue()));
reloadScannerPreferences();
}
});
this.reloadScannerPreferences();
}
protected void initializeProjectPreferencesAndListener(IProject project) {
this.projectPreferences = new ProjectPreferences(project);
this.projectPreferences.addListener(new IPreferenceChangeListener() {
public void preferenceChange(PreferenceChangeEvent changeEvent) {
log.info("Project preference changed [{}]: <{}> -> <{}>",
o(changeEvent.getKey(), changeEvent.getOldValue(), changeEvent.getNewValue()));
reloadProjectPreferences();
requestCacheReload();
}
});
this.reloadProjectPreferences();
}
protected void reloadScannerPreferences() {
try {
classScannerPreferences.load();
} catch (BackingStoreException e) {
log.error("Failed to load scanner preferences", e);
}
}
private void reloadProjectPreferences() {
try {
projectPreferences.load();
} catch (BackingStoreException e) {
log.error("Failed to load project preferences", e);
}
Locale storyLocale = LocaleUtils.createLocaleFromCode(projectPreferences.getStoryLanguage(), Locale.ENGLISH);
localizedStepSupport.setStoryLocale(storyLocale);
parameterPrefix = projectPreferences.getParameterPrefix();
log.info("Reloading project preferences, story locale: {}, parameter prefix: {}", storyLocale, parameterPrefix);
}
public void addListener(JBehaveProjectListener listener) {
listeners.add(listener);
}
public void removeListener(JBehaveProjectListener listener) {
if(listener==null)
return;
listeners.remove(listener);
}
public LocalizedStepSupport getLocalizedStepSupport() {
return localizedStepSupport;
}
public Locale getLocale() {
return getLocalizedStepSupport().getLocale();
}
private Callback<IMethod, Container<StepCandidate>> newCallback() {
return new Callback<IMethod, Container<StepCandidate>>() {
public void op(IMethod method, final Container<StepCandidate> container) {
StepCandidateReduceListener listener = getStepCandidateReduceListener(container);
MethodToStepCandidateReducer reducer =
new MethodToStepCandidateReducer();
try {
reducer.reduce(method, listener);
} catch (JavaModelException e) {
log.error("Failed to add step candidates for method {}", method, e);
}
}
};
}
private StepCandidateReduceListener getStepCandidateReduceListener(
final Container<StepCandidate> container) {
return new StepCandidateReduceListener() {
public void add(IMethod method, StepType stepType, String stepPattern,
Integer priority) {
container.add(new StepCandidate(getLocalizedStepSupport(),
parameterPrefix, method, stepType, stepPattern, priority));
}
};
};
public void notifyChanges(IJavaElementDelta delta) {
int kind = delta.getKind();
log.debug("Notifying change within project {}: {} ({})", o(project.getName(), delta, Integer.toBinaryString(kind)));
requestCacheReload();
}
public IProject getProject() {
return project;
}
public ProjectPreferences getProjectPreferences() {
return projectPreferences;
}
public StepLocator getStepLocator() {
return new StepLocator(this);
}
public void traverseSteps(Visitor<StepCandidate, ?> visitor) throws JavaModelException {
if (this.cache == null) {
this.cache = createMethodCache();
requestCacheReload();
}
log.debug("Traversing cache for project " + project.getName());
this.cache.traverse(visitor);
}
private void requestCacheReload() {
IJavaProject javaProject = (IJavaProject)JavaCore.create(project);
this.cacheLoader.requestReload(createMethodCache(), javaProject, getMethodCacheScanInitializer());
}
private Effect<JavaScanner<?>> getMethodCacheScanInitializer() {
return new Effect<JavaScanner<?>>() {
@Override
public void e(JavaScanner<?> scanner) {
scanner.setFilterHash(classScannerPreferences.calculateHash());
scanner.setPackageRootNameFilter(classScannerPreferences
.getPackageRootMatcher());
scanner.setPackageNameFilter(classScannerPreferences
.getPackageMatcher());
scanner.setClassNameFilter(classScannerPreferences
.getClassMatcher());
}
};
}
private static Executor getSystemJobAsExecutor(final String jobName) {
return new Executor() {
public void execute(Runnable command) {
JBehaveProject.runRunnableAsSystemJob(command, jobName);
}
};
}
private static void runRunnableAsSystemJob(final Runnable runnable, final String jobName) {
Job job = new Job(jobName) {
@Override
protected IStatus run(IProgressMonitor monitor) {
runnable.run();
return Status.OK_STATUS;
}
};
job.setUser(false);
job.setSystem(true);
job.schedule();
}
/** {@inheritDoc} */
public void cacheLoaded(MethodCache<StepCandidate> cache) {
this.cache = cache;
for (JBehaveProjectListener listener : listeners) {
try {
listener.stepsUpdated();
} catch (Exception e) {
log.error("Error during step invalidation notification: {}",
listener, e);
}
}
}
}