6

I created an eclipse plugin that will hook into the save action to create a minified javascript file with the goolge closure compiler. See files below. That worked until eclipse 3.7.2. Unfortunately now in eclipse 4.2.1 it seems that this creates an endless loop sometimes. The job "compile .min.js" (line 64 in ResourceChangedListener.java) seems the be the cause. It results in the case that the workspaced starts to build over and over. I guess this is because that job creates or changes a file triggering the workspace build again, which again triggers the job which triggers the build and so on. But I can not figure out how to prevent this.

// Activator.java

package closure_compiler_save;

import org.eclipse.core.resources.ResourcesPlugin;
import org.eclipse.ui.plugin.AbstractUIPlugin;
import org.osgi.framework.BundleContext;

/**
 * The activator class controls the plug-in life cycle
 */
public class Activator extends AbstractUIPlugin  {

    // The plug-in ID
    public static final String PLUGIN_ID = "closure-compiler-save"; //$NON-NLS-1$

    // The shared instance
    private static Activator plugin;

    /**
     * The constructor
     */
    public Activator() {
    }

    @Override
      public void start(BundleContext context) throws Exception {
        super.start(context);
        Activator.plugin = this;

        ResourceChangedListener listener = new ResourceChangedListener();
          ResourcesPlugin.getWorkspace().addResourceChangeListener(listener);
    }

    @Override
      public void stop(BundleContext context) throws Exception {
        Activator.plugin = null;
        super.stop(context);
    }

    /**
     * Returns the shared instance
     *
     * @return the shared instance
     */
    public static Activator getDefault() {
        return plugin;
    }
}

// ResourceChangedListener.java

package closure_compiler_save;

import java.io.ByteArrayInputStream;
import java.io.IOException;
import java.io.InputStream;

import org.eclipse.core.resources.IFile;
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.runtime.CoreException;
import org.eclipse.core.runtime.IPath;
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;

public class ResourceChangedListener implements IResourceChangeListener {

    public void resourceChanged(IResourceChangeEvent event) {
        if (event.getType() != IResourceChangeEvent.POST_CHANGE)
            return;

        IResourceDelta delta = event.getDelta();
        try {
            processDelta(delta);
        } catch (CoreException e) {
            e.printStackTrace();
        }
    }

    // find out which class files were just built
    private void processDelta(IResourceDelta delta) throws CoreException {

        IResourceDelta[] kids = delta.getAffectedChildren();
        for (IResourceDelta delta2 : kids) {
            if (delta2.getAffectedChildren().length == 0) {
                if (delta.getKind() != IResourceDelta.CHANGED)
                    return;

                IResource res = delta2.getResource();
                if (res.getType() == IResource.FILE && "js".equalsIgnoreCase(res.getFileExtension())) {
                    if (res.getName().contains("min"))
                        return;
                    compile(res);
                }
            }
            processDelta(delta2);
        }
    }

    private void compile(final IResource res) throws CoreException {

        final IPath fullPath = res.getFullPath();
        final IPath fullLocation = res.getLocation();
        final String fileName = fullPath.lastSegment().toString();
        final String outputFilename = fileName.substring(0, fileName.lastIndexOf(".")).concat(".min.js");
        final String outputPath = fullPath.removeFirstSegments(1).removeLastSegments(1).toString();

        final IProject project = res.getProject();
        final IFile newFile = project.getFile(outputPath.concat("/".concat(outputFilename)));
        Job compileJob = new Job("Compile .min.js") {
            public IStatus run(IProgressMonitor monitor) {
                byte[] bytes = null;
                try {
                    bytes = CallCompiler.compile(fullLocation.toString(), CallCompiler.SIMPLE_OPTIMIZATION).getBytes();

                    InputStream source = new ByteArrayInputStream(bytes);
                    if (!newFile.exists()) {
                        newFile.create(source, IResource.NONE, null);
                    } else {
                        newFile.setContents(source, IResource.NONE, null);
                    }
                } catch (IOException e) {
                    e.printStackTrace();
                } catch (CoreException e) {
                    e.printStackTrace();
                }
                return Status.OK_STATUS;
            }
        };
        compileJob.setRule(newFile.getProject()); // this will ensure that no two jobs are writing simultaneously on the same file
        compileJob.schedule();
    }

}
steros
  • 1,794
  • 2
  • 26
  • 60

1 Answers1

3

After I setup a blank eclipse classic environment, started a new eclipse plugin project there and recreated all files it works again partly. In this environment starting a debug session I can save .js files and .min.js files are created automatically. So far so good! But when I install the plugin to my real developing eclipse environment automatic saving does not work.

At least one step further!

Step 2: There were some files not included in the build obviously needed, like manifest. No idea why they were deselected. Anyway it seems just setting up a blank eclipse 4 classic and going through the eclipse plugin wizard fixed my original problem. I still would love to know what was the actual problem...

steros
  • 1,794
  • 2
  • 26
  • 60
  • Where does CallCompiler come from? Is that from a 3rd party jar that you included in your Eclipse plugin folder maybe in a /lib dir? If you did, be sure that the 3rd party jar is on the Manifest.mf runtime path (edited in the manifest.mf editor) and also make sure that on the build.properties in the bin.includes it will include that 3rd party jar. – gamerson Mar 14 '13 at 07:04
  • CallCompiler is my own class, it calls the closure compiler for the file in question. The logic in that class works. I also have a popup menu that triggers the minify process for a file with the same CallCompiler class. Just that automatic saving process won't work anymore. – steros Mar 15 '13 at 08:21
  • Strange, so it works in your workspace/runtime worksbench but not standalone install. I've seen that many times. Looking at the code, be careful with the res.getLocation() call. I've seen that return null in some situations. My only suggestion now is to just add more debug output for your compile() method to see what the differences are between dev version and installed version. – gamerson Mar 15 '13 at 09:02
  • I found the error, the manifest itself (and other files) were not included in the build although the 3rd party jar was, I added that myself. That's why it didn't work. – steros Mar 15 '13 at 09:46