Monday Jan 07, 2013

Savable in Node Based MultiViewElement

Some time ago, Timon Veenstra from Ordina wrote about how to create MultiViewElements for Nodes. A follow up question is... how to enable the Save Action for MultiViewElements that have been created for Nodes?

The constructor of a MultiViewElement always receives a Lookup. But how to add a Saveable object to that Lookup so that the Save Action can become enabled?

My solution is weird, but the only thing that I can think of: put the InstanceContent into the Node's Lookup, i.e., add InstanceContent to InstanceContent:

public class CustomerNode extends BeanNode<Customer> implements Serializable {
    
    public CustomerNode(Customer bean) throws IntrospectionException {
        this(bean, new InstanceContent());
    }
    
    private CustomerNode(Customer bean, InstanceContent ic) throws IntrospectionException {
        super(bean, Children.LEAF, new AbstractLookup(ic));
        ic.add(ic);
        setDisplayName(bean.getName());
    }

    @Override
    public Action getPreferredAction() {
        return new AbstractAction("Edit") {
            @Override
            public void actionPerformed(ActionEvent e) {
                TopComponent tc = MultiViews.createMultiView("application/x-customernode", CustomerNode.this);
                tc.open();
                tc.requestActive();
            }
        };
    }

}

Now, in your MultiViewElement, you can get the InstanceContent from the Lookup that you receive in the constructor. That Lookup is defined by the Lookup in the Node. Therefore, because there's an InstanceContent in the Node Lookup (as you can see above), the InstanceContent is now in the MultiViewElement:

@MultiViewElement.Registration(
        displayName = "General",
        mimeType = "application/x-customernode",
        persistenceType = TopComponent.PERSISTENCE_NEVER,
        preferredID = "CustomerNodeGeneralElement",
        position = 100)
public class GeneralPanel extends JPanel implements MultiViewElement {

    private final Lookup lookup;
    private final InstanceContent ic;
    
    public GeneralPanel(Lookup lookup) {
        this.lookup = lookup;
        this.ic = lookup.lookup(InstanceContent.class);
        ...
        ...
        ...

And now, anywhere in the MultiViewElement, you can add/remove the Savable to/from the InstanceContent whenever needed. That will trigger the Save Action to enable/disable itself.

Sunday Jan 06, 2013

Apache Camel Endpoints for Annotated Code Completion Sources

Olivier Schmitt from the French Ministry of Agriculture has forked and enhanced the code I recently blogged about for creating annotations for generating code completion support for NetBeans-based editors.

His fork is here:

https://github.com/olivier-schmitt/netbeans-poc/tree/master/completion/xhtml/annotated/CompletionRegistrationSupport

He reports that "the new thing is about telling the completion provider registration layer what is 'source' for completion items. My idea is to use URI 'a la Apache Camel' to declare item provider sources:"

@AttributeCompletionRegistrations({
   
    @AttributeCompletionRegistration(
        id = "jee-architect-cookbook-netbeans-iso6391-LanguageAttributeCompletionProvider",
        attribute = "lang",
        iconBase = "jee/architect/cookbook/netbeans/iso6391/bubble.png",
        content = "csv:jee/architect/cookbook/netbeans/iso6391/ISO6391.csv"),
   
    @AttributeCompletionRegistration(
        id = "jee-architect-cookbook-netbeans-iso6391-CountriesAttributeCompletionProvider",
        attribute = "accesskey",
        iconBase = "jee/architect/cookbook/netbeans/iso6391/bubble.png",
        content = "rest:http://api.worldbank.org/country?format=json"),
    
    @AttributeCompletionRegistration(
        id = "jee-architect-cookbook-netbeans-iso6391-BlaAttributeCompletionProvider",
        attribute = "bla",
        iconBase = "jee/architect/cookbook/netbeans/iso6391/bubble.png",
        content = "raw:tom,dick,harry"),
    
    @AttributeCompletionRegistration(
        id = "jee-architect-cookbook-netbeans-iso6391-CountriesAttributeCompletionProvider",
        attribute = "bla2",
        iconBase = "jee/architect/cookbook/netbeans/iso6391/bubble.png",
        content = "bean:jee.architect.cookbook.netbeans.iso6391.Bla2CompletionItemProvider") 
})
package jee.architect.cookbook.netbeans.iso6391;

import org.netbeans.spi.editor.completion.xhtml.api.AttributeCompletionRegistration;
import org.netbeans.spi.editor.completion.xhtml.api.AttributeCompletionRegistrations;

Source of the above:

https://github.com/olivier-schmitt/netbeans-poc/blob/master/completion/xhtml/annotated/CompletionRegistrationSupport/iso6391-sample/src/jee/architect/cookbook/netbeans/iso6391/package-info.java

Olivier says that he thinks "there should be a strong separation between UI artifacts and source provider artifacts. So, the content URI just tells how to get items and not how to render them."

Sounds like a great enhancement!

Saturday Jan 05, 2013

On PowerJava: "Roles" Instead Of "Objects"

While looking for a good candidate language for creating tutorials around language support using the NetBeans APIs, I recently came across the PowerJava project.

I was kind of curious about the background and purpose of this language and so contacted Fabio Filippi, from the Università degli Studi di Torino, the developer behind the language (pictured, right). I asked him a couple of questions.

Hi Fabio, how did PowerJava come to be?

I started working on PowerJava in 2011. This language was thought up by Guido Boella and Matteo Baldoni. It was initially sketched out by Erik Arnaudo in his bachelor thesis, but he didn't finish the work and used Java 1.3 grammar. I completely changed the code and the patterns used, using Java 1.5 grammar, and added relationships and other solutions. With that thesis I took the highest grade, 110/110 and honors.

And what does PowerJava consist of?

PowerJava is a role-oriented extension of Java 1.5. "Roles" substitute "Objects" as first-class citizen. Roles lead to being able to add "Relationships" as primitive entities of the language, reducing the gap between object oriented languages and relationship oriented languages (like SQL).

This is the pattern used to implement Roles:

This is the pattern us to implement Relationships:

There are many new reserved words in PowerJava: 

  •     definerole: the type of a role
  •     role: the interface of a role
  •     relationship: the type of a relationship
  •     from/to: role of the binary relationship
  •     outer: institution of a certain role
  •     that: actor of a certain role
  •     tuple: tuple of a relationship
  •     universe: table of all relationship 
  •     playedby: interface that can play a certain role.

Why are Roles necessary and what do they have that Objects don't?

Roles are necessary when talking about a reality that changes, such as a society. Objects are too static to represents situations like institutions and users. For example, a guest becomes a user that becomes a customer.

Here are two examples: 

  • The first is without relationships, Persona.pj; here part of it is displayed in the NetBeans PowerJava editor:



  • The second, Unito.pj, extends the first by adding relationships:

The second example solves problems such as "how to check if the student and the professor are in the same school?" and "how to know that a professor is not a student of him/herself?" and "where to save votes that are of a couple (student,course)?"

Finally, in Printer.pj you can find a more standard example.

What's the current status of PowerJava?

To be honest, I'm not using PowerJava anymore. I abandoned the project and haven't seen my thesis professor since that time. He probably also left the project. I developed PowerJava for my bachelor thesis and now I'm writing my masters thesis on a different subject.

Nevertheless, thanks for the interview Fabio, PowerJava has some really interesting concepts! (And it's perfect as the basis of NetBeans editor API, i.e., lexing and parsing, tutorials.)

Friday Jan 04, 2013

Getting Started with Diff Viewer from Scratch

Let's get started creating an application from scratch integrating the NetBeans Diff Viewer. Create a new application:

Give it a name and store it somewhere:

Create a new module:

Give it a name and store it somewhere:

Give it a unique identifier and display name:

Right-click the application, choose Properties, go to Libraries, and select "Diff" from the "ide" cluster and then click the Resolve button to include the related modules:

Create a new Action:

Let it always be enabled:

Specify the locations where the Action will be invoked:

Provide class name, display name, and icon:

Right-click the module and specify that you want to add a dependency:

Add a dependency on the Diff module:

Do the same as the above for the Window System module.

Define the Action as follows, replacing the hardcoded files with your own:

package org.my.diff;

import java.awt.BorderLayout;
import java.awt.event.ActionEvent;
import java.awt.event.ActionListener;
import java.io.File;
import java.io.IOException;
import javax.swing.SwingUtilities;
import org.netbeans.api.diff.Diff;
import org.netbeans.api.diff.DiffView;
import org.netbeans.api.diff.StreamSource;
import org.openide.awt.ActionID;
import org.openide.awt.ActionReference;
import org.openide.awt.ActionRegistration;
import org.openide.util.NbBundle.Messages;
import org.openide.windows.TopComponent;

@ActionID(
        category = "Tools",
        id = "org.my.diff.DiffViewerAction")
@ActionRegistration(
        asynchronous = true,
        displayName = "#CTL_DiffViewerAction")
@ActionReference(
        path = "Menu/Tools", 
        position = 0)
@Messages("CTL_DiffViewerAction=Open Diff Viewer")
public final class DiffViewerAction implements ActionListener {

     @Override
    public void actionPerformed(ActionEvent e) {
        StreamSource local = StreamSource.createSource("name1",
                "title1", "text/html", new File("C:/tutorials/nbm-google.html"));
        StreamSource remote = StreamSource.createSource("name2",
                "title2", "text/html", new File("C:/tutorials/72/nbm-google.html"));
        diff(local, remote);
    }

    public void diff(final StreamSource local, final StreamSource remote) {
        SwingUtilities.invokeLater(new Runnable() {
            @Override
            public void run() {
                try {
                    DiffView view = Diff.getDefault().createDiff(local, remote);
                    TopComponent tc = new TopComponent();
                    tc.setDisplayName("Diff Viewer");
                    tc.setLayout(new BorderLayout());
                    tc.add(view.getComponent(), BorderLayout.CENTER);
                    tc.open();
                    tc.requestActive();
                } catch (IOException ex) {
                }
            }
        });
    }
    
}

Alternatively, let's assume that the files we want to diff are within the module, for some reason, rather than externally on disk. This is how the "actionPerformed" above would be rewritten:

@Override
public void actionPerformed(ActionEvent e) {
    StreamSource ss1 = null;
    StreamSource ss2 = null;
    InputStreamReader ir1 = null;
    InputStreamReader ir2 = null;
    try {
        ir1 = new InputStreamReader(FileUtil.getConfigFile("files/one").getInputStream());
        ir2 = new InputStreamReader(FileUtil.getConfigFile("files/two").getInputStream());
        ss1 = StreamSource.createSource("name1", "title1", "text/html", ir1);
        ss2 = StreamSource.createSource("name2", "title2", "text/html", ir2);
    } catch (FileNotFoundException ex) {
        Exceptions.printStackTrace(ex);
    }
    diff(ss1, ss2);
}

And here are the related layer elements:

<folder name="files">
    <file name="one" url="file1.html"/> 
    <file name="two" url="file2.html"/> 
</folder>

The above assumes the layer.xml is in the same package as the two files "file1.html" and "file2.html".

Run the application and go to the Tools menu to invoke the Action:

You'll see this:

Go back to the Project Properties dialog of the application and include these two modules from the "ide" cluster:

Clean the application. Build the application. Run the application. Invoke the Action. You'll see the Diff Viewer, as below, diffing the two files you passed in:

Thursday Jan 03, 2013

Code Folding via NetBeans, JJTree, and PowerJava

Once you have a ParserResult, as described yesterday, you also need to have access to some kind of AST node, i.e., not only tokens. Tokens let you distinguish between individual characters in a document, but that's not enough if you want to create code folding for your editor. Below you see code folding for PowerJava, which is an extension to Java 1.5 for role programming. It has about 8 special keywords, which in my editor are shown in orange, as can be seen for two of them below:

The first code fold, i.e., the initial code fold, is created automatically for you if you use the CSL API. However, the other code fold you see above was created via code in the related StructureScanner:

class PowerJavaStructureScanner implements StructureScanner {

    public static final String CODE_FOLDS = "codeblocks";
    
    @Override
    public List<? extends StructureItem> scan(ParserResult pr) {
        return new ArrayList<StructureItem>();
    }

    @Override
    public Map<String, List<OffsetRange>> folds(ParserResult pr) {
        HashMap<String, List<OffsetRange>> folds = new HashMap<String, List<OffsetRange>>();
        PowerJavaParserResult pjpr = (PowerJavaParserResult) pr;
        Document document = pr.getSnapshot().getSource().getDocument(false);
        try {
            ASTCompilationUnit cu = pjpr.getASTCompilationUnit();
            Node[] children = cu.getChildren();
            for (int i = 0; i < children.length; i++) {
                Node node = children[i];
                if (node instanceof ASTTypeDeclaration) {
                    ASTTypeDeclaration asttd = (ASTTypeDeclaration) node;
                    Token jjtGetFirstToken = asttd.jjtGetFirstToken();
                    Token jjtGetLastToken = asttd.jjtGetLastToken();
                    int start = NbDocument.findLineOffset((StyledDocument) document, 
                            jjtGetFirstToken.beginLine - 1) + jjtGetFirstToken.beginColumn - 1;
                    int end = NbDocument.findLineOffset((StyledDocument) document, 
                            jjtGetLastToken.endLine - 1) + jjtGetLastToken.endColumn;
                    String type = CODE_FOLDS;
                    OffsetRange range = new OffsetRange(start, end);
                    List<OffsetRange> fold = folds.get(type);
                    if (fold == null) {
                        fold = new ArrayList<OffsetRange>();
                    }
                    fold.add(range);
                    folds.put(type, fold);
                }
            }
        } catch (org.netbeans.modules.parsing.spi.ParseException ex) {
            Exceptions.printStackTrace(ex);
        }
        return folds;
    }

    @Override
    public Configuration getConfiguration() {
        return new Configuration(false, false);
    }

}

The above implies that you have higher level objects, such as ASTCompilationUnit. You can get those via JJTree, but only if you have defined a related JJT file, in addition to your JJ file. And that's why I am using PowerJava as an example, as can be seen above, because the nice people who created PowerJava have all the relevant source code available. I simply had to run javacc on their Java1.5.jjt file, and then I had everything I needed. I then integrated their lexer into the NetBeans lexer infrastructure, which enabled me to create the syntax coloring you see above. Then I used the generated parser as shown above, i.e., I can now obtain meaningful information via the generated parser so that code folds can be created.

Wednesday Jan 02, 2013

org.netbeans.modules.csl.api.StructureScanner

When using the CSL API, you should not use org.netbeans.spi.editor.fold.FoldManager directly. It is too heavyweight, when compared to the alternative provided by the CSL API:  org.netbeans.modules.csl.api.StructureScanner.

If you have a class like this, i.e., extending org.netbeans.modules.csl.spi.DefaultLanguageConfig and using the org.netbeans.modules.csl.spi.LanguageRegistration annotation, then you can pass in a StructureScanner, as shown below:

package org.simplejava;

import org.netbeans.api.lexer.Language;
import org.netbeans.modules.csl.api.StructureScanner;
import org.netbeans.modules.csl.spi.DefaultLanguageConfig;
import org.netbeans.modules.csl.spi.LanguageRegistration;
import org.netbeans.modules.parsing.spi.Parser;
import org.simplejava.lexer.SJTokenId;
import org.simplejava.parser.SJParser;
import org.simplejava.parser.SJStructureScanner;

@LanguageRegistration(mimeType = "text/x-sj")
public class SJLanguage extends DefaultLanguageConfig {

    @Override
    public Language<SJTokenId> getLexerLanguage() {
        return SJTokenId.getLanguage();
    }

    @Override
    public String getDisplayName() {
        return "SJ";
    }

    @Override
    public Parser getParser() {
        return new SJParser();
    }

    @Override
    public StructureScanner getStructureScanner() {
        return new SJStructureScanner();
    }

    @Override
    public boolean hasStructureScanner() {
        return true;
    }

}

Then here's a dummy implementation of the above:

package org.simplejava.parser;

import java.util.ArrayList;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
import org.netbeans.modules.csl.api.OffsetRange;
import org.netbeans.modules.csl.api.StructureItem;
import org.netbeans.modules.csl.api.StructureScanner;
import org.netbeans.modules.csl.api.StructureScanner.Configuration;
import org.netbeans.modules.csl.spi.ParserResult;

public class SJStructureScanner implements StructureScanner {

    @Override
    public List<? extends StructureItem> scan(ParserResult pr) {
        return new ArrayList<>();
    }

    @Override
    public Map<String, List<OffsetRange>> folds(ParserResult pr) {
        return new HashMap<>();
    }

    @Override
    public Configuration getConfiguration() {
        return null;
    }
    
}

When you look in GsfFoldManager.java, which any CSL plugin automatically includes thanks to the @LanguageRegistration annotation above, you'll see that from the DefaultLanguageConfig extension the StructureScanner is located and an internal FoldManager implementation is used to get the folds defined in StructureScanner.folds(), after which all the necessary things are done for you.

Some default folds are created automatically by the internal FoldManager implementation, such as for the initial comment, as shown below:

As the ParserResult from your parser is passed to the SS.folds() method, you should be able to get the folds since the parser result should contain a parse tree where the code block is described by some node in the tree.

Note: For the above to work, your ParserResult class must extend org.netbeans.modules.csl.spi.ParserResult and NOT org.netbeans.modules.parsing.spi.parser.Result. Just return Collections.EMPTY_LIST from getDiagnostics initially.

Useful implementations:

Thanks to Marek Fukala for help with the above. 

Tuesday Jan 01, 2013

JSON World Bank Data in NetBeans HTML Code Completion

Continuing from yesterday, another source of data for code completion could be data extracted from JSON via an online RESTful web service, such as this one:

http://api.worldbank.org/country?format=json

I'm not trying to make the argument that it's useful to integrate World Bank data into the code completion box in the NetBeans HTML Editor, but simply that it's possible to do so (and you might have, more realistically, customer data exposed via a RESTful web service that you'd like to have available while working with a specific HTML attribute) via the annotation I described yesterday:

@HTMLAttributeCompletionRegistration(
        id = "jee-architect-cookbook-netbeans-iso6391-CountriesAttributeCompletionProvider",
        attribute = "accesskey",
        iconBase = "jee/architect/cookbook/netbeans/iso6391/bubble.png",
        content = "http://api.worldbank.org/country?format=json",
        contentType="json")

That's all the code needed because the layer entries generated from the above create a code completion provider with this result:

Again, the "accesskey" attribute doesn't make sense in the context of the above data, but it's just an example of what's possible.

I had to cheat slightly because I haven't figured out a good way to pass in the path to the required data yet, so I've hardcoded it, as you can see below in the "loadContent" method of the completion provider to which the above annotation maps, as described yesterday:

private void loadContent(String content, String contentType) throws IOException {
    if (contentType.equals("csv")) {
        ClassLoader classLoader = Thread.currentThread().getContextClassLoader();
        InputStream inputStream = classLoader.getResourceAsStream(content);
        if (inputStream != null) {
            BufferedReader bufferedReader = new BufferedReader(new InputStreamReader(inputStream));
            String line = null;
            while ((line = bufferedReader.readLine()) != null) {
                codes.add(line);
            }
        }
    } else if (contentType.equals("json")) {
        try {
            URL url = new URL(content);
            HttpURLConnection conn = (HttpURLConnection) url.openConnection();
            conn.setRequestMethod("GET");
            conn.setRequestProperty("Accept", "application/json");
            if (conn.getResponseCode() != 200) {
                StatusDisplayer.getDefault().setStatusText(
                        "Failed : HTTP error code : "
                        + conn.getResponseCode());
            }
            BufferedReader br = new BufferedReader(
                    new InputStreamReader((conn.getInputStream())));
            JSONArray array = (JSONArray) JSONValue.parse(br);
            JSONArray next = (JSONArray) array.get(1);
            for (int i = 0; i < next.size(); i++) {
                    //Map m = (Map) ((JSONObject) next.get(i)).get("region");
                    //String region = m.get("value").toString();
                    //codes.add(region);
                String country = (String) ((JSONObject) next.get(i)).get("name");
                String capitalCity = (String) ((JSONObject) next.get(i)).get("capitalCity");
                codes.add(capitalCity + "/" + country);
            }
            conn.disconnect();
        } catch (MalformedURLException e) {
            Exceptions.printStackTrace(e);
        } catch (IOException e) {
            Exceptions.printStackTrace(e);
        }
    } else if (contentType.equals("basic")) {
        codes.addAll(Arrays.asList(content.split(",")));
    }
}

All the casting above seems a bit ugly but I don't know a better way to do it and, again again, that's not the point here.

Some related info that I found helpful:

And the code above is now part of the download referred to yesterday:

http://java.net/projects/nb-api-samples/sources/api-samples/show/versions/7.3/misc/CompletionRegistrationSupport
About

Geertjan Wielenga (@geertjanw) is a Principal Product Manager in the Oracle Developer Tools group living & working in Amsterdam. He is a Java technology enthusiast, evangelist, trainer, speaker, and writer. He blogs here daily.

The focus of this blog is mostly on NetBeans (a development tool primarily for Java programmers), with an occasional reference to NetBeans, and sometimes diverging to topics relating to NetBeans. And then there are days when NetBeans is mentioned, just for a change.

Search

Archives
« January 2013 »
SunMonTueWedThuFriSat
  
25
26
27
30
  
       
Today