Monday, August 5, 2013

Import Eclipse formatter into Netbeans

Currently I am required to only commit files if they were formatted by Eclipse using a custom formatter settings file, being a netbeans lover, I started looking for a way to format my java source when it is saved (as Eclipse does).

This led me to Netbeans's default formatter, Jindent and even opening up Eclipse to format just before committing my code, the first two being in vain as they could not match the custom format and the last option being very tedious.

Enter lots of research:
how-to-use-the-eclipse-code-formatter-from-your-code
maven-java-formatter-plugin

Using the links above as my base, I started working on a Netbeans plugin to do what I would like to accomplish, this is what I came up with: a working netbeans plugin using a custom eclipse formatter xml file to format the code.

First we need to create a new Netbeans module and ensure the following API's are added:
























The eclipse JARs are available in your eclipse installation folder and should be added as wrapped jar's (right click on the project to do this)

I should now mention that the following code was taken and/or modified from the above links and that credit is due to the original authors were it was used.

Here is a screenshot of the directory structure:
























The whole config package was obtained from the maven-java-formatter-plugin, the rest was created and or modified for the netbeans plugin:

EclipseFormatter.java
package com.epoch.eclipse.formatter;
 
import com.epoch.eclipse.formatter.config.ConfigReader;
import java.io.IOException;
import java.io.InputStream;
import java.util.HashMap;
import java.util.Map;
import java.util.logging.Level;
import java.util.logging.Logger;
import org.eclipse.jdt.core.JavaCore;
import org.eclipse.jdt.core.ToolFactory;
import org.eclipse.jdt.core.formatter.CodeFormatter;
import org.eclipse.jface.text.BadLocationException;
import org.eclipse.jface.text.Document;
import org.eclipse.jface.text.IDocument;
import org.eclipse.text.edits.MalformedTreeException;
import org.eclipse.text.edits.TextEdit;
 
public final class EclipseFormatter
{
  private static final String CONFIG_LOCATION = "/resources/CustomFormat.xml";
  private static final String COMPILER_VERSION = "1.6";
  private Map formatterOptions;
  private CodeFormatter formatter;
 
  public EclipseFormatter()
  {
    this.formatterOptions = getFormattingOptions();
    this.formatter = ToolFactory.createCodeFormatter(this.formatterOptions);
  }
 
  private static Map getOptionsFromConfigFile()
  {
    final ConfigReader configReader = new ConfigReader();
    final InputStream configInput = EclipseFormatter.class.getResourceAsStream(CONFIG_LOCATION);
    Map options = new HashMap();
    if (configInput != null)
    {
      try
      {
        options = configReader.read(configInput);
      }
      catch (Exception e)
      {
        Logger.getLogger(EclipseFormatter.class.getName()).log(Level.SEVERE,
            "error reading configuration " + "options from[" + CONFIG_LOCATION + "]", e);
      }
      finally
      {
        try
        {
          configInput.close();
        }
        catch (IOException e)
        {
        }
      }
    }
    return options;
  }
 
  private static Map getFormattingOptions()
  {
    Map options = new HashMap();
    options.put(JavaCore.COMPILER_SOURCE, COMPILER_VERSION);
    options.put(JavaCore.COMPILER_COMPLIANCE, COMPILER_VERSION);
    options.put(JavaCore.COMPILER_CODEGEN_TARGET_PLATFORM, COMPILER_VERSION);
    options.putAll(getOptionsFromConfigFile());
    return options;
  }
 
  private String format(final String code) throws MalformedTreeException, BadLocationException
  {
    final int opts = CodeFormatter.K_COMPILATION_UNIT + CodeFormatter.F_INCLUDE_COMMENTS;
    final TextEdit te = this.formatter.format(opts, code, 0, code.length(), 0, null);
    final IDocument dc = new Document(code);
    String formattedCode = code;
    if (te != null)
    {
      te.apply(dc);
      formattedCode = dc.get();
    }
    return formattedCode.toString();
  }
 
  public String forCode(final String code)
  {
    String result = code;
    try
    {
      if (code != null)
      {
        result = this.format(code);
      }
    }
    catch (MalformedTreeException ex)
    {
      Logger.getLogger(EclipseFormatter.class.getName()).log(Level.SEVERE,
          "code could not be formatted!", ex);
    }
    catch (BadLocationException ex)
    {
      Logger.getLogger(EclipseFormatter.class.getName()).log(Level.SEVERE,
          "code could not be formatted!", ex);
    }
    return result;
  }
}

ReformatWithEclipseBeforeSaveTask.java

package com.epoch.eclipse.formatter;
 
import java.util.ArrayList;
import java.util.Collections;
import java.util.List;
import java.util.Set;
import java.util.concurrent.atomic.AtomicBoolean;
import javax.swing.text.AbstractDocument;
import javax.swing.text.BadLocationException;
import javax.swing.text.Document;
import javax.swing.text.JTextComponent;
import org.netbeans.api.editor.EditorRegistry;
import org.netbeans.api.editor.mimelookup.MimeRegistration;
import org.netbeans.api.lexer.LanguagePath;
import org.netbeans.api.lexer.TokenHierarchy;
import org.netbeans.spi.editor.document.OnSaveTask;
import org.openide.util.Exceptions;
 
public class ReformatWithEclipseBeforeSaveTask implements OnSaveTask
{
  private final Document document;
  private EclipseFormatter formatter = new EclipseFormatter();
  private AtomicBoolean canceled = new AtomicBoolean();
 
  ReformatWithEclipseBeforeSaveTask(Document doc)
  {
    this.document = doc;
  }
 
  @Override
  public void performTask()
  {
    if (this.formatter != null)
    {
      reFormat();
    }
  }
 
  @Override
  public void runLocked(Runnable run)
  {
    run.run();
  }
 
  @Override
  public boolean cancel()
  {
    canceled.set(true);
    return true;
  }
 
  private boolean isJava()
  {
    boolean result = false;
    TokenHierarchy th = TokenHierarchy.get(this.document);
    Set<languagepath> languagePathSet = Collections.emptySet();
    if (this.document instanceof AbstractDocument)
    {
      AbstractDocument adoc = (AbstractDocument) this.document;
      adoc.readLock();
      try
      {
        languagePathSet = th.languagePaths();
        List<languagepath> languagePaths = new ArrayList<languagepath>(languagePathSet);
        for (LanguagePath lp : languagePaths)
        {
          if (lp.mimePath().endsWith("text/x-java"))
          {
            result = true;
            break;
          }
        }
      }
      finally
      {
        adoc.readUnlock();
      }
    }
    return result;
  }
 
  private void reFormat()
  {
    int caret = -1;
    JTextComponent editor = EditorRegistry.lastFocusedComponent();
    if (editor != null)
    {
      caret = editor.getCaretPosition();
    }
    final int length = this.document.getLength();
    String result = null;
    try
    {
      if (isJava())
      {
        String docText = null;
        try
        {
          docText = this.document.getText(0, length);
        }
        catch (BadLocationException ex)
        {
          Exceptions.printStackTrace(ex);
        }
        result = formatter.forCode(docText);
      }
    }
    finally
    {
      if (result != null)
      {
        try
        {
          this.document.remove(0, length);
          this.document.insertString(0, result, null);
          if (editor != null && caret > -1)
          {
            editor.setCaretPosition(caret);
          }
        }
        catch (BadLocationException ex)
        {
          Exceptions.printStackTrace(ex);
        }
      }
    }
  }
 
  @MimeRegistration(mimeType = "", service = OnSaveTask.Factory.class, position = 1500)
  public static final class FactoryImpl implements Factory
  {
    @Override
    public OnSaveTask createTask(Context context)
    {
      return new ReformatWithEclipseBeforeSaveTask(context.getDocument());
    }
  }
}

Anyway, hope this helps someone, please feel free to use this in any way possible, an official plugin would be awesome but unfortunately I do not have enough time at the moment to finish the job, this is a rough implementation :)

UPDATE
This has now been formed as a fully featured plugin, available on Github

2 comments :

  1. nice

    https://blogs.oracle.com/geertjan/entry/eclipse_formatter_for_netbeans_ide

    ReplyDelete
  2. Your post is big help for me. Thanks you so much :)

    ReplyDelete