package com.redhat.ceylon.eclipse.ui;
import static com.redhat.ceylon.eclipse.core.builder.CeylonBuilder.CHARSET_PROBLEM_MARKER_ID;
import static com.redhat.ceylon.eclipse.java2ceylon.Java2CeylonProxies.modelJ2C;
import static com.redhat.ceylon.eclipse.util.InteropUtils.toCeylonString;
import static com.redhat.ceylon.eclipse.util.InteropUtils.toJavaString;
import static org.eclipse.core.resources.IResource.DEPTH_ONE;
import static org.eclipse.core.resources.IResourceDelta.CONTENT;
import static org.eclipse.core.resources.IResourceDelta.ENCODING;
import static org.eclipse.core.resources.IResourceDelta.OPEN;
import static org.eclipse.core.resources.ResourcesPlugin.getWorkspace;
import java.util.concurrent.atomic.AtomicBoolean;
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.IResourceChangeEvent;
import org.eclipse.core.resources.IResourceChangeListener;
import org.eclipse.core.resources.IResourceDelta;
import org.eclipse.core.resources.IResourceDeltaVisitor;
import org.eclipse.core.resources.IWorkspace;
import org.eclipse.core.resources.IWorkspaceRoot;
import org.eclipse.core.resources.WorkspaceJob;
import org.eclipse.core.runtime.CoreException;
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 com.redhat.ceylon.eclipse.core.builder.CeylonBuilder;
import com.redhat.ceylon.eclipse.core.builder.CeylonNature;
import com.redhat.ceylon.ide.common.model.CeylonProject;
import com.redhat.ceylon.ide.common.model.CeylonProjectConfig;
public class CeylonEncodingSynchronizer {
private static final CeylonEncodingSynchronizer instance = new CeylonEncodingSynchronizer();
public static CeylonEncodingSynchronizer getInstance() {
return instance;
}
private final IResourceChangeListener resourceChangeListener = new InternalResourceChangeListener();
private final IResourceDeltaVisitor resourceDeltaVisitor = new InternalResourceDeltaVisitor();
private final AtomicBoolean isSuspended = new AtomicBoolean(false);
public boolean suspend() {
return isSuspended.getAndSet(true);
}
public void unsuspend(boolean old) {
isSuspended.set(old);
}
public void refresh(IResource resource, IProgressMonitor monitor) {
boolean old = suspend();
try {
resource.refreshLocal(IResource.DEPTH_INFINITE, monitor);
}
catch (CoreException e) {
e.printStackTrace();
}
finally {
unsuspend(old);
}
}
public void install() {
getWorkspace().addResourceChangeListener(resourceChangeListener);
}
public void uninstall() {
getWorkspace().removeResourceChangeListener(resourceChangeListener);
}
private void synchronizeEncoding(IProject project, boolean forceEclipseEncoding) {
if (!(project.isAccessible() && CeylonNature.isEnabled(project))) {
return;
}
try {
removeProblemMarker(project);
String eclipseEncoding = project.getDefaultCharset();
String configEncoding = toJavaString(modelJ2C().ceylonModel().getProject(project)
.getConfiguration().getProjectEncoding());
if (forceEclipseEncoding) {
updateEncoding(project, eclipseEncoding);
return;
}
if (configEncoding == null) {
updateEncoding(project, eclipseEncoding);
}
else if (!configEncoding.equalsIgnoreCase(eclipseEncoding)) {
createProblemMarker(project, eclipseEncoding, configEncoding);
// showSynchronizationDialog(project, eclipseEncoding, configEncoding);
}
} catch (CoreException e) {
throw new RuntimeException(e);
}
}
private void removeProblemMarker(final IProject project) {
Job job = new Job("Remove character encoding problem marker") {
@Override
protected IStatus run(IProgressMonitor monitor) {
try {
project.deleteMarkers(CHARSET_PROBLEM_MARKER_ID, false, DEPTH_ONE);
}
catch (CoreException e) {
e.printStackTrace();
}
return Status.OK_STATUS;
}
};
job.setRule(project);
job.schedule();
}
private void createProblemMarker(final IProject project,
final String eclipseEncoding, final String configEncoding) {
Job job = new Job("Create character encoding problem marker") {
@Override
protected IStatus run(IProgressMonitor monitor) {
try {
// project.deleteMarkers(CHARSET_PROBLEM_MARKER_ID, false, DEPTH_ONE);
IMarker marker = project.createMarker(CHARSET_PROBLEM_MARKER_ID);
marker.setAttribute(IMarker.PRIORITY, IMarker.PRIORITY_HIGH);
marker.setAttribute(IMarker.SEVERITY, IMarker.SEVERITY_ERROR);
marker.setAttribute(IMarker.LOCATION, project.getName());
marker.setAttribute(IMarker.SOURCE_ID, CeylonBuilder.SOURCE);
marker.setAttribute(IMarker.MESSAGE, getMessage(project,
eclipseEncoding, configEncoding));
}
catch (CoreException e) {
e.printStackTrace();
}
return Status.OK_STATUS;
}
};
job.setRule(project);
job.schedule();
}
// private void showSynchronizationDialog(final IProject project, final String eclipseEncoding, final String configEncoding) {
// Display.getDefault().asyncExec(new Runnable() {
// @Override
// public void run() {
// MessageDialog dialog = new MessageDialog(
// null, "Encoding settings synchronization?",
// null, getMessage(project, eclipseEncoding, configEncoding) +
// ". \n\nWhich encoding do you want to use?",
// MessageDialog.QUESTION,
// new String[] {
// "Use '" + eclipseEncoding.toLowerCase() + "'",
// "Use '" + configEncoding.toLowerCase() + "'" }, 0);
//
// int result = dialog.open();
// if (result == 0) {
// updateEncoding(project, eclipseEncoding);
// } else {
// updateEncoding(project, configEncoding);
// }
// }
//
// });
// }
private static String getMessage(final IProject project,
final String eclipseEncoding, final String configEncoding) {
return "character encoding is out of sync: project "
+ project.getName() + " is "
+ eclipseEncoding +
" but " + project.getFullPath() + "/.ceylon/config specifies "
+ configEncoding + "\n" +
" The project cannot be built if the encoding is not synchronized. Use the Quick Fix to synchronize it."
;
}
public void updateEncoding(IProject project, String encoding) {
new InternalSynchronizeJob(project, encoding).schedule();
}
private class InternalResourceChangeListener implements IResourceChangeListener {
@Override
public void resourceChanged(IResourceChangeEvent event) {
if( !isSuspended.get() ) {
try {
if (event.getDelta() != null) {
event.getDelta().accept(resourceDeltaVisitor);
}
} catch (CoreException e) {
throw new RuntimeException(e);
}
}
}
}
private class InternalResourceDeltaVisitor implements IResourceDeltaVisitor {
@Override
public boolean visit(IResourceDelta delta) throws CoreException {
IResource resource = delta.getResource();
if (resource instanceof IWorkspace || resource instanceof IWorkspaceRoot) {
return true;
} else if (resource instanceof IFolder && resource.getName().equals(".ceylon")) {
return true;
} else if (resource instanceof IProject) {
if (hasFlag(delta, OPEN)) {
synchronizeEncoding((IProject) resource, false);
return false;
}
if (hasFlag(delta, ENCODING)) {
synchronizeEncoding((IProject) resource, true);
return false;
}
return true;
} else if (resource instanceof IFile) {
if (hasFlag(delta, CONTENT)) {
IProject project = resource.getProject();
String filePath = resource.getProjectRelativePath().toString();
if (filePath.equals(".project") /* handle adding ceylon nature to existing project */) {
synchronizeEncoding(project, false);
}
if (filePath.equals(".ceylon/config")) {
CeylonProject<IProject,IResource,IFolder,IFile> ceylonProject = modelJ2C().ceylonModel().getProject(project);
if (ceylonProject != null) {
ceylonProject.getConfiguration().refresh();
synchronizeEncoding(project, false);
}
}
}
}
return false;
}
}
private class InternalSynchronizeJob extends WorkspaceJob {
private final IProject project;
private final String encoding;
private InternalSynchronizeJob(IProject project, String encoding) {
super("Synchronize project encoding configuration");
this.project = project;
this.encoding = encoding;
}
@Override
public IStatus runInWorkspace(IProgressMonitor monitor) throws CoreException {
refresh(project.getFolder(".settings"), monitor);
refresh(project.getFolder(".ceylon"), monitor);
CeylonProject<IProject,IResource,IFolder,IFile> ceylonProject = modelJ2C().ceylonModel().getProject(project);
if (ceylonProject != null) {
CeylonProjectConfig config = ceylonProject.getConfiguration();
config.refresh();
try {
isSuspended.set(true);
String originalEclipseEncoding = project.getDefaultCharset();
if (!isEquals(originalEclipseEncoding, encoding)) {
project.setDefaultCharset(encoding, monitor);
}
String originalConfigEncoding = toJavaString(config.getProjectEncoding());
if (!isEquals(originalConfigEncoding, encoding)) {
config.setProjectEncoding(toCeylonString(encoding));
config.save();
}
} finally {
isSuspended.set(false);
}
}
return Status.OK_STATUS;
}
}
private boolean hasFlag(IResourceDelta delta, int flag) {
return ((delta.getFlags() & flag) == flag);
}
private boolean isEquals(String encoding1, String encoding2) {
return encoding1 != null ? encoding1.equalsIgnoreCase(encoding2) : false;
}
}