Sunday, November 30, 2008

Groovy as extension for Netbeans editor - source code

Hi!
You can download source code of my previous tutorial here.

Alex: Sorry for late.

Cheers! :)

Saturday, October 4, 2008

Groovy as extension for Netbeans editor

As you know I work a lot with xml files (see my Xml/Xsl posts). Xml files are perfect for storing informations in structured way. Sometimes there is need for refactoring those informations. By refactoring I mean nodes/attributes renaming, changing value or place of those. If file is too big, that task may be very-very boring doing it by hand. Of course you can use your favorite text editor's search-replace feature but for complex tasks you will end writing utility for processing xml.
I played a little bit with Groovy and GroovyShell and noticed that you can easily pass your (java) variables to it. It means that you can pass your editor's document and process it with Groovy.
I created simple module for processing xml files. It is based on XPathEvaluator module. It adds a new window to Netbeans where you can write Groovy scripts.


Code for "Run Script" button looks like this:
Binding binding = new Binding();
binding.setVariable("doc", xmlDoc);
binding.setVariable("output", jTextArea1);
GroovyShell shell = new GroovyShell(binding);
shell.evaluate(jEditorPane1.getText());

It binds xmlDoc, which is EditorCookie.getDocument(), to doc variable and evaluates the script which is in jEditorArea.

Maybe a Groovy could be a new way for extending Netbeans? Tell me what you think.

Saturday, August 23, 2008

SubnetCalculator JM2E

I just found this little utility on my computer and decided to share it. It is a j2me subnet calculator midlet for the mobile phone. I made it when I was attending a Cisco courses (2 years ago). I tested it on Samsung D500 (CLDC1.0 and MIDP 2.0) at that time. If jar is not working you can recompile it with Netbeans 6.5 and Sun Java Wireless Toolkit 2.5.2 for CLDC.



Download: jar and source code

I also found some interesting stuff: my antivirus (Avira AntiVir) software is reporting that Java Wireless Toolkit is infected by "BDS/Backdoor.Gen" back-door program when I try to run emulator.exe.

Monday, June 2, 2008

Why I haven't been blogging much lately

Hello everybody!

Well, I was occupied with school and work. Last two months I was programming my diploma work :) and next week I have final exam. I just wanted to say hello and show you what I did using Netbeans.



What you see is an interface of program for modeling the spatial and temporal distribution of concentration changes caused by the diffusion and chemical reactions. I'm specially proud on feature which shows the model in 3D while you type the code. Something like JavaFX editor.
Some technologies I struggled with:
  • Web Service
  • XML/XSD
  • OpenGL

Now I have to learn for next week.
Bye :)

Saturday, March 29, 2008

Netbeans Platform based Web Service client

I'm writing my diploma thesis and I have choosed Netbeans Platform as base for my program. It will be client-server application. Client will invoke web service methods provided by server. My troubles started here. I strugled a lot with these kind of messages:

interface com.sun.xml.ws.developer.WSBindingProvider is not visible from class loader

Unable to create Provider: com.sun.xml.ws.spi.ProviderImpl cannot be cast to javax.xml.ws.spi.Provider

There is no exact tutorial (for Netbeans 6.x and JAX-WS 2.1) describing how to invoke web service from Netbeans Platform application.

So here are steps:
  1. Create web service and client using this tutorial
  2. Create library wrapper module for web service client (you don't need to include JAX-WS libs, only your client jar)
  3. In your wrapper module add following dependencies (important):
    • JAX-WS 2.1 API
    • JAX-WS 2.1 and JAXB 2.1 Library (for this you have to check Show Non-API Modules in "Add Module Dependency" window)
  4. If you try to build module after these steps it will fail telling you that your module is not friend of "path-to-netbeans"/java2/modules/org-netbeans-modules-websvc-jaxws21.jar
  5. Right click on JAX-WS 2.1 and JAXB 2.1 Library and choose Edit. Select Implementation Version.
That's all. Happy web service-ing :) By the way it would be nice if we could add web service client in module project like we do that in j2se project. There would not be need for client wrapper.

Tuesday, March 4, 2008

Netbeans T-shirt

I've got it. Thank you James! :)

Sunday, February 24, 2008

File opening: EditorCookie or OpenCookie?

Yesterday I wanted to create XML document in memory and open it. On Netbeans Wiki there is explanation and sample code for creating text file and opening it using OpenCookie. But the sample code does not work with XML documents. I debugged the code and noticed that XmlDataObject does not contain OpenCookie class. Instead we have to use EditorCookie. And EditorCookie also works with text files too. So maybe the sample code on Netbeans Wiki should be updated to following:
    FileSystem fs = FileUtil.createMemoryFileSystem();
    FileObject fob = fs.getRoot().createData(name, "txt");

    DataObject data = DataObject.find(fob);
    EditorCookie cookie = (EditorCookie)data.getCookie(EditorCookie.class);
    cookie.open();
Feel free to comment.

Tuesday, February 12, 2008

XPathEvaluator source code

You can download source code of my previous tutorial here. Cheers! :)

Monday, February 4, 2008

XPath evaluator module for Netbeans

Introduction

I do a lot XML and Xslt processing at my workplace. Altova XmlSpy has XPath tool for evaluating queries on edited XML file. I'm missing that (useful) feature in Netbeans so I decided to build something similar. Of course it's far away from Altova's version in features but it can be useful (and you can always extend it).

Creating module project

  1. New project: Choose File > New Project (Ctrl-Shift-N). Under Categories, select NetBeans Modules. Under projects, select Module and click Next.
  2. In the Name and Location panel, type XPathEvaluator in Project Name. Change the Project Location to any directory on your computer, such as /home/damir/Projects/netbeans. Leave the Standalone Module radio button selected.
  3. In the Basic Module Configuration panel, replace yourorghere in Code Name Base with netbeans.modules, so that the whole code name base is org.netbeans.modules.xpathevaluator. Leave the location of the localizing bundle and XML layer, so that they will be stored in a package with the name org/netbeans/modules/xpathevaluator.
  4. Click finish.

Add module dependencies

In project tree right click on Libraries Node > Add module dependency...

  1. Import Datasystems API by typing datasystems
    It contains XmlDataObject which we will listen for.

Create a TopComponent for evaluating queries and displaying results

  1. New file (Ctrl-N). Under Categories, select Module Development. Under file types, select Window Component and click Next.
  2. Window Position: output
  3. Leave Open on Application start unchecked
  4. Class Name Prefix: XPathEvaluator
  5. Package: org.netbeans.modules.xpathevaluator
  6. Click finish
Now lets build interface in design view. We have to add following controls:
  • 2x JLabel
  • 1x JTextField for XPath input
  • 1x JTextArea for XPath results
TopComponent should look like this:

Create action for displaying XPathEvaluatorTopComponent

  1. New file (Ctrl-N). Under Categories, select Module Development. Under file types, select Action and click Next.
  2. Check conditionally enabled and choose EditorCookie for cookie class
  3. GUI registration:
    • Category: XML
    • Uncheck Global Menu Item
    • Check Editor Context Menu and choose text/xml for content type, change position to Tools - HERE
    • Check Separator before and Separator after
  4. Name, Icon, and Location:
    • Class Name: ShowXPathEvaluatorAction
    • Display Name: XPath Evaluator
    • Package Name: org.netbeans.modules.xpathevaluator
  5. Click finish
When user choose XPathNavigator item from xml's context menu we will show him XPathEvaluatorTopComponent but before showing we will create org.w3c.dom.Document from editorCookie's input stream and pass it.
PerformAction method should look like this:

protected void performAction(Node[] activatedNodes)
{
    EditorCookie editorCookie = activatedNodes[0].getLookup().lookup(EditorCookie.class);

    try
    {
        DocumentBuilderFactory domFactory = DocumentBuilderFactory.newInstance();
        domFactory.setNamespaceAware(true);
        DocumentBuilder builder = domFactory.newDocumentBuilder();
        Document doc = builder.parse(((org.openide.text.CloneableEditorSupport) editorCookie).getInputStream());

        XPathEvaluatorTopComponent win = XPathEvaluatorTopComponent.findInstance();
        win.setDocument(doc);
        win.open();
        win.requestActive();
    }
    catch (SAXException ex)
    {
    //Exceptions.printStackTrace(ex);
    }
    catch (ParserConfigurationException ex)
    {
    //Exceptions.printStackTrace(ex);
    }
    catch (IOException ex)
    {
    //Exceptions.printStackTrace(ex);
    }
}
      
Press Ctrl+I to fix imports. Make sure you select org.w3c.dom.Document from combo box.

Extending XPathEvaluatorTopComponent

We have to:

  • create private member which will hold org.w3c.dom.Document from selected editor and on which we will execute XPath.
  • create setDocument(org.w3c.dom.Document doc) method for setting current xml document.
  • create evaluate() method for executing and displaying XPath query.
  • add DocumentListener to jTextField1 for executing XPath query as we type.
  • listen for changes in TopComponent selection (when user opens/activates/closes other xml or non xml panel).
I will focus on last three points and let you struggle with other two :)

evaluate() method

This method is simple. It evaluates text from jTextField1 control and fills jTextArea1 control with results after clearing the same. If there were errors it shows them at bottom of window (jLabel2). xmlDoc is current xml document.

private void evaluate()
{
    if (xmlDoc != null)
    {
        try
        {
            XPathFactory factory = XPathFactory.newInstance();
            XPath xpath = factory.newXPath();
            XPathExpression expr = xpath.compile(jTextField1.getText());

            NodeList nodes = (NodeList) expr.evaluate(xmlDoc, XPathConstants.NODESET);
            jTextArea1.setText("");
            for (int i = 0; i < nodes.getLength(); i++)
            {
                jTextArea1.setText(jTextArea1.getText() + nodes.item(i).getNodeValue() + "\n");
            }
        }
        catch (XPathExpressionException ex)
        {
            jLabel2.setText(ex.getMessage());
        }
    }
}
      

DocumentListener for XPath input

Append following code at the end of XmlPathEvaluatorTopComponent's constructor:

jTextField1.getDocument().addDocumentListener(new DocumentListener()
{
    public void insertUpdate(DocumentEvent e)
    {
        evaluate();
    }

    public void removeUpdate(DocumentEvent e)
    {
        evaluate();
    }

    public void changedUpdate(DocumentEvent e)
    {
    }
});
      

Listen for changes in Lookup

Following code is based on Netbeans Selection Managment Tutorial I. We have to listen for global selection changes of "Object". In our case it is XmlDataObject. We have to extend our TopComponent to implement LookupListener.

final class XPathEvaluatorTopComponent extends TopComponent implements LookupListener
      
LookupListener provides a method for handling changes (resultChanged(LookupEvent ev)). We have to check if there are any instances of XmlDataObject selected and change xmlDoc value according to that.
  • If there are any XmlDataObject instances (in this case only one) then we have set xmlDoc member's value to selected XmlDataObject's XmlDocument and we have to enable input in window.
  • If there are not any instances then we have to set it to null and to disable input on window.
If we put only these two checks then we won't get waiting result. Supose that user selects xml file. If he change focus to XPathEvaluator window, Lookup event will be fired and there won't be any XmlDataObject selected and our module will be unusable.
To avoid this we have to check if our window (XPathEvaluator) is selected.
public void resultChanged(LookupEvent arg0)
{
    // if this window selected then do nothing
    if (TopComponent.getRegistry().getActivated().equals(this))
    {
    // do nothing
    }
    else
    {
        Lookup.Result r = (Lookup.Result) arg0.getSource();

        Collection c = r.allInstances();
        // if there are instances
        if (!c.isEmpty())
        {
            try
            {
                XMLDataObject o = (XMLDataObject) c.iterator().next();
                setDocument(o.getDocument());
                evaluate();
                jLabel2.setText(o.getName());
                jTextArea1.setEnabled(true);
                jTextField1.setEnabled(true);
            }
            catch (IOException ex)
            {
                jLabel2.setText(ex.getMessage());
            }
            catch (SAXException ex)
            {
                jLabel2.setText(ex.getMessage());
            }
        }
        else
        {
            xmlDoc = null;
            jTextArea1.setEnabled(false);
            jTextField1.setEnabled(false);
            jLabel2.setText("no selection");
        }
    }
}
      
Now we have to register LookupListener. We will do that when window is opened:
@Override
public void componentOpened()
{
    Lookup.Template tpl = new Lookup.Template(org.openide.loaders.XMLDataObject.class);
    result = Utilities.actionsGlobalContext().lookup(tpl);
    result.addLookupListener(this);
}
      
When we close window we have to unregister the listener:
@Override
public void componentClosed()
{
    result.removeLookupListener(this);
    result = null;
}
      
We have to create a member for holding the result where lookup listener is registered. You can find explanation for this in Netbeans Selection Managment Tutorial I.

Result

Monday, January 7, 2008

Busy

I'm a little bit busy right now but I'm planning to post another tutorial soon. Thanks for reading! :)

Saturday, December 29, 2007

Thursday, December 20, 2007

DDL Visualizer - Visualize sql script with Netbeans

Creating module project

  1. New project: Choose File > New Project (Ctrl-Shift-N). Under Categories, select NetBeans Modules. Under projects, select Module and click Next.
  2. In the Name and Location panel, type DDLVisualizer in Project Name. Change the Project Location to any directory on your computer, such as c:\mymodules. Leave the Standalone Module radio button selected.
  3. In the Basic Module Configuration panel, replace yourorghere in Code Name Base with myorg, so that the whole code name base is org.myorg.ddlvisualizer. Leave the location of the localizing bundle and XML layer, so that they will be stored in a package with the name org/myorg/ddlvisualizer.
  4. Click finish.

Add module dependencies (you will need them later)

In project tree right click on Libraries Node > Add module dependency...

  1. Import Visual Library API by typing visual
  2. Import Generic Languages Framework by typing languages

Create language support

  1. New file (Ctrl-N). Under Categories, select Module Development. Under file types, select Language Support and click Next.
  2. In the Mime Type and extensions panel, type text/x-fbsql and in the extensions type fbsql. Our new file will have fbsql extension.
  3. Click finish.
Project tree should look like this:
I already created simple ddl parser for table creation (it's Firebird database syntax). Overwrite content of language.nbs file with following code:
# DDL Visualizer 
# created by Damir Tesanovic (tdamir.blogspot.com) 

TOKEN:keyword:("create" | "table" | "constraint" | "unique" | "primary" 
                | "key" | "foreign" | "references" 
                | "smallint" | "integer" | "float" | "double" | "precision" 
                | "decimal" | "numeric" | "date" | "time" | "timestamp" 
                | "char" | "varchar" | "blob") 

TOKEN:identifier:( ["a"-"z" "A"-"Z" "_"] ["a"-"z" "A"-"Z" "0"-"9" "_"]* ("." ["a"-"z" "A"-"Z" "_"] ["a"-"z" "A"-"Z" "0"-"9" "_"]*)? ) 
TOKEN:identifier:( "\"" [^ "\""]* "\"" ) 

TOKEN:operator:("<=" | "<>" | "<" | "=" | ">=" | ">" | "||" | "-" | "," | ";" | ":" | "/" | "." | "(" | ")" | "[" | "]" | "*" | "+") 

TOKEN:whitespace:( [" " "\t" "\n" "\r"]+ ) 
TOKEN:number:(["0"-"9"]+) 

# parser should ignore whitespaces 
SKIP:whitespace 

# definition of grammar 
S = (CreateTable)*; 

CreateTable = "create" "table" TableName TableDef; 

TableDef = "(" ColumnDef ("," (ColumnDef | TableConstraint))* ")"; 

ColumnDef = ColumnName DataType; 

TableConstraint = ["constraint" ConstraintName] 
                    (("unique" | "primary" "key") "(" ColumnName ("," ColumnName)* ")" | ForeignKeyClause) 
                    ; 

ForeignKeyClause = "foreign" "key" "(" ColumnName ("," ColumnName)* ")" ReferencesClause; 

ConstraintName = <identifier>; 
TableName = <identifier>; 
ColumnName = <identifier>; 
Integer = <number> ; 

ReferencesClause = "references" TableName ["(" ColumnName ("," ColumnName)* ")"]; 

DataType = "smallint" | "integer" | "float" | "double" "precision" 
            | ( "decimal" | "numeric" ) [ "(" Integer [ "," Integer ] ")" ] 
            | "date" | "time" | "timestamp" 
            | ( "char" | "varchar" ) [ "(" Integer ")" ] 
            | "blob" 
            ; 

# error highlighting 
MARK:ERROR: { 
type:"Error"; 
message:"Syntax error."; 
} 

MARK:error: { 
type:"Error"; 
message:"Unexpected character."; 
} 

Create action for displaying diagram:

  1. New file (Ctrl-N). Under Categories, select Module Development. Under file types, select Action and click Next.
  2. Check conditionally enabled and choose EditorCookie for cookie class
  3. GUI registration:
    • Category: Tools
    • Uncheck Global Menu Item
    • Check File Type Context Menu and choose Languages for content type, change position to Properties-HERE
    • Check Editor Context Menu and choose text/x-sql for content type (we will modify it in next section)
  4. Name, Icon, and Location:
    • Class Name: ShowDiagram
    • Display Name: Show Diagram
    • Package Name: org.myorg.ddlvisualizer
  5. Click finish
Replace section Editors in layer.xml with this:
<folder name="Editors"> 
 <folder name="text"> 
  <folder name="x-fbsql"> 
   <file name="language.nbs" url="language.nbs"/> 
   <folder name="Popup"> 
    <file name="org-myorg-ddlvisualizer-ShowDiagram.shadow"> 
     <attr name="originalFile" stringvalue="Actions/Tools/org-myorg-ddlvisualizer-ShowDiagram.instance"/> 
     <attr name="position" intvalue="700"/> 
    </file> 
   </folder> 
  </folder> 
 </folder> 
</folder>
     
We will add functionality to created action later.

Creating scene for diagram

  1. New file (Ctrl-N). Under Categories, select Java. Under file types, select Java Class and click Next.
  2. Class Name: DDLVisualScene, Package: org.myorg.ddlvisualizer
  3. Click finish.
Antoni Epple created excellent tutorial about creating Visual Database Explorer from database metadata using Netbeans. You can find it here. I modified his DBGraphScene class to suite my needs.
Replace text in file with following:
package org.myorg.ddlvisualizer;

import java.awt.Image;
import java.awt.Point;
import java.util.ArrayList;
import java.util.Iterator;
import org.netbeans.api.languages.ASTNode;
import org.netbeans.api.visual.action.ActionFactory;
import org.netbeans.api.visual.vmd.VMDGraphScene;
import org.netbeans.api.visual.vmd.VMDNodeWidget;
import org.netbeans.api.visual.vmd.VMDPinWidget;

/** 
 * 
 * @author Damir Tesanovic (based on Anton Epple's DBGraphScene) 
 */
public class DDLVisualScene extends VMDGraphScene {

    private static int edgeID = 1;

    public DDLVisualScene() {
        this.getActions().addAction(ActionFactory.createZoomAction());
        this.getActions().addAction(ActionFactory.createPanAction());

    }

    public void setASTNode(ASTNode node) {
        for (ASTNode table : getNodes(node, "CreateTable", false)) {
            String tableName = table.getNode("TableName").getTokenTypeIdentifier("identifier");
            createNode(this, (int) (Math.random() * 800), (int) (Math.random() * 800), tableName, "Table", null);

            for (ASTNode col : getNodes(table, "ColumnDef", true)) {
                String columnName = col.getNode("ColumnName").getTokenTypeIdentifier("identifier");
                createPin(this, tableName, tableName + ":" + columnName, columnName, columnName);
            }

            for (ASTNode foreignKeyClause : getNodes(table, "ForeignKeyClause", true)) {
                ArrayList<String> cols = new ArrayList<String>();
                for (ASTNode col : getNodes(foreignKeyClause, "ColumnName", false)) {
                    cols.add(col.getTokenTypeIdentifier("identifier"));
                }

                String refTableName = foreignKeyClause.getNode("ReferencesClause").getNode("TableName").getTokenTypeIdentifier("identifier");
                ArrayList<String> refCols = new ArrayList<String>();
                for (ASTNode refCol : getNodes(foreignKeyClause.getNode("ReferencesClause"), "ColumnName", false)) {
                    refCols.add(refCol.getTokenTypeIdentifier("identifier"));
                }

                for (int i = 0; i < cols.size(); i++) {
                    createEdge(this, refTableName + ":" + refCols.get(i), tableName + ":" + cols.get(i));
                }

            }
        }

        this.moveTo(null);

    }

    private ArrayList<ASTNode> getNodes(ASTNode root, String nt, boolean deep) {
        ArrayList<ASTNode> nodes = new ArrayList<ASTNode>();
        fillNodes(root, nodes, nt, deep);
        return nodes;
    }

    private void fillNodes(ASTNode node, ArrayList<ASTNode> out, String nt, boolean deep) {
        Iterator it = node.getChildren().iterator();
        while (it.hasNext()) {
            Object elem = it.next();
            if (elem instanceof ASTNode) {
                if (((ASTNode) elem).getNT().equals(nt)) {
                    out.add((ASTNode) elem);
                }
                if (deep) {
                    fillNodes((ASTNode) elem, out, nt, deep);
                }
            }
        }
    }

    private static String createNode(VMDGraphScene scene, int x, int y, String name, String type, java.util.List<Image> glyphs) {
        String nodeID = name;
        if (!scene.getNodes().contains(nodeID)) {
            VMDNodeWidget widget = (VMDNodeWidget) scene.addNode(nodeID);
            widget.setPreferredLocation(new Point(x, y));
            widget.setNodeProperties(null, name, type, glyphs);
        }
        return nodeID;
    }

    private static void createPin(VMDGraphScene scene, String nodeID, String pinID, String name, String type) {
        if (!scene.getPins().contains(pinID)) {
            ((VMDPinWidget) scene.addPin(nodeID, pinID)).setProperties(name, null);
        }
    }

    private static void createEdge(VMDGraphScene scene, String sourcePinID, String targetPinID) {
        String edgeID = "edge" + DDLVisualScene.edgeID++;
        scene.addEdge(edgeID);
        System.out.println("createEdge " + sourcePinID + "<->" + targetPinID);
        scene.setEdgeSource(edgeID, sourcePinID);
        scene.setEdgeTarget(edgeID, targetPinID);
    }

    private void moveTo(Point point) {
        int index = 0;
        for (String node : getNodes()) {
            getSceneAnimator().animatePreferredLocation(findWidget(node), point != null ? point : new Point(++index * 100, index * 100));
        }
    }
} 
It has setASTNode(ASTNode node) method which goes through AST tree and builds diagram.

Create a Top Component for diagram

  1. New file (Ctrl-N). Under Categories, select Module Development. Under file types, select Window Component and click Next.
  2. Window Position: editor
  3. Leave Open on Application start unchecked
  4. Class Name Prefix: Visualizer
  5. Package: org.myorg.ddlvisualizer
  6. Click finish
Project tree should look like this:
Add JScrollPane to VisualizerTopComponent by dragging it from palette to panel.
Modify source code:
  1. Create private instance of DDLVisualScene
  2. Create public getter method for DDLVisualScene instance
  3. Add at the end of TopComponent constructor: jScrollPane1.setViewportView(scene.createView());
VisualizerTopComponent.java should look like this after modifications:
private static final String PREFERRED_ID = "VisualizerTopComponent";
private DDLVisualScene scene = new DDLVisualScene();

private VisualizerTopComponent() {
 initComponents();
 setName(NbBundle.getMessage(VisualizerTopComponent.class, "CTL_VisualizerTopComponent"));
 setToolTipText(NbBundle.getMessage(VisualizerTopComponent.class, "HINT_VisualizerTopComponent"));
 // setIcon(Utilities.loadImage(ICON_PATH, true)); 
 jScrollPane1.setViewportView(scene.createView());
}

Modify ShowDiagram.java

Thanks to Generic Languages Framework we can easily get AST tree from document. We will pass it to scene and then show VisualizerTopComponent. Replace performAction method with following code:

protected void performAction(Node[] activatedNodes) {
 try {
  EditorCookie editorCookie = activatedNodes[0].getLookup().lookup(EditorCookie.class);
  Document doc = editorCookie.getDocument();
  ParserManager pm = ParserManager.get(doc);

  VisualizerTopComponent win = VisualizerTopComponent.findInstance();
  win.getScene().setASTNode(pm.getAST());
  win.open();
  win.requestActive();
 } catch (ParseException ex) {
  Exceptions.printStackTrace(ex);
 }
}

sample.fbsql

Here is sample file for testing.

create table EMPLOYEE ( 
    EMP_NO integer, 
    FIRST_NAME varchar, 
    LAST_NAME varchar 
) 

create table PROJECT ( 
    PROJ_ID integer, 
    PROJ_NAME varchar, 
    PROJ_DESC varchar, 
    TEAM_LEADER integer, 
    
    constraint fkey1 foreign key (TEAM_LEADER) references EMPLOYEE (EMP_NO) 
) 

create table EMPLOYEE_PROJECT ( 
    EMP_NO integer, 
    PROJ_ID integer, 

    constraint fkey1 foreign key (EMP_NO) references EMPLOYEE (EMP_NO), 
    constraint fkey2 foreign key (PROJ_ID) references PROJECT (PROJ_ID) 
) 
 

Result

Sunday, December 16, 2007

Hello world!

My first post on my first blog :)