Showing posts with label Java. Show all posts
Showing posts with label Java. Show all posts

Monday, June 15, 2009

Java: JOptionPane Examples Part 4 - Complex

Break out of the JOptionPane Limitations with JDialog

In the final article in my JOptionPane example series, I will show how to eliminate a JOptionPane limitation that you may encounter. The limitation is size. Sure, the dialog automatically sizes itself to fit your GUI elements. However, there is no way to allow a user to resize the standard JOptionPane dialog.

The way to eliminate this problem is to embed a JOptionPane object within a JDialog. It's very simple and requires very little change to existing code you may have.

Example 1: Resizable and Editable JTextArea with JScrollPane scrollbars

This example expands upon Example 2 (from Part 2). A JTextArea is used, but the dialog is not resizable. By embedding with a JDialog, this limitation can be overcome.

The definition of the JTextArea and Object array remains the same:

    JTextArea area = new JTextArea();
area.setText("line1\nline2\nline3\nline4\nline5\nline6");
area.setRows(5);
area.setColumns(10);
area.setEditable(true);
JScrollPane scrollpane = new JScrollPane(area);

Object[] array = {
new JLabel("Enter some text:"),
scrollpane,
};
 

Instead of using one of the static methods of JOptionPane, a JOptionPane object is created using its constructor. The Object array and the message type are passed as arguments to the constructor.

    JOptionPane pane = new JOptionPane(array, JOptionPane.PLAIN_MESSAGE);
 

Once the JOptionPane object has been created, the createDialog() method is called, which returns a JDialog object. null is passed to createDialog() to center the dialog on the screen, while the string argument will be the title of the dialog.

    JDialog dialog = pane.createDialog(null, "Result Data");
 

Before the setVisible() method is called, setResizable() is called to allow the dialog to be resized. When setVisible() is called, the dialog will exhibit the same general behavior as when using the static methods of JOptionPane.

    dialog.setResizable(true);
dialog.setVisible(true);
 

Once the user has closed the dialog, control returns to the program. As with the original version of the program, the JTextArea object still exists and its value can be obtained using getText().

    String newtext = area.getText();
System.out.println( "newtext: " + newtext );
 

With JDialog, many new options are available.

Wednesday, June 10, 2009

J-Link: Getting Started with Java and Pro/Engineer, Part 2

J-Link Java Code Requirements

J-Link requires that you have static methods with names matching the java_app_start and java_app_stop options from the registry file. Using the example registry file from Part 1, the required signatures for the "start" and "stop" methods would look like this:

    public static void startApp();
public static void stopApp();
 

As you'll see from the code that follows, these static methods don't do much but call other methods where all the important stuff happens. This approach seems to work well, but there are certainly other ways to approach J-Link.

Method Summary:

Here are the other methods used by the program, none of which are static:

Constructor JLinkHelloWorld()

The constructor sets up the needed utility items, such as the program name (based on the class name), a FileWriter object for the log file, and the platform specific newline character.

Method startupApplication()

The startupApplication() method gets the Pro/Engineer session object, defines a UI command, registers the UI command in the GUI (in the 'Tools' menu), and then announces to the user that the program is operational.

Method shutdownApplication()

The shutdownApplication() method closes out the log file's FileWriter object.

Method writeLog(String mesg)

A log file can be used for debugging and reporting purposes. A developer will use it for debugging an application, while an end user will be able to see what the program is doing internally.

Although using the log file is definitely optional, you'll find that there are all sorts of bizarre things in Pro/Engineer models that you will never have anticipated. With the use of proper logging, a developer increases the chances that these anomalies can be reported back by the end user.

Method closeLog()

As you might expect, this closes the log file.

Method DisplayMessage(String mesg)

DisplayMessage() writes an arbitrary message to the Pro/Engineer message area for the user to see. The example uses of DisplayMessage() that I have provided is not really the best approach for internationalization, but it serves the purpose of showing how to write message to the Pro/Engineer GUI. This method also writes the same output to the log file.

UI Command Registration and UI Listeners

A program is not going to be useful to an end user unless they can run the program. The user is not going to be able to run a J-Link program unless it registers itself with the Pro/Engineer GUI via a new menu or new menu option.

UI command registration is a multistep action requiring a couple of different pieces. The pieces required are a UICommandActionListener and a callback method, while the actions involve creating a command using UICreateCommand() and adding the command to the GUI using UIAddButton(). The text strings used as arguments to the UI methods are either found in the application's text message file (i.e. "JLHW Btn1 Label") or are known by Pro/Engineer (i.e. "Utilities" which represents the "Tools" menu).

The example program implements the listener by defining an inner class that extends the DefaultUICommandActionListener class. The inner class has a single method OnCommand() that is triggered when the user clicks on the command in the GUI. OnCommand() calls Btn1_callback() which is in the main class. Btn1_callback() then gets the current model object, if any, and displays the name in the message window using DisplayMessage().


The J-Link "Hello World" Java Code:

// JLinkHelloWorld.java
// Copyright 2009, MarcMettes@InversionConsulting.com

// imports required
import com.ptc.cipjava.*;
import com.ptc.pfc.pfcCommand.*;
import com.ptc.pfc.pfcGlobal.*;
import com.ptc.pfc.pfcModel.*;
import com.ptc.pfc.pfcSession.*;
import java.io.*;

public class JLinkHelloWorld {

static JLinkHelloWorld App = null;
String programName = null;
Session session = null;
FileWriter log = null;
String msgFile = "msg_jlinkhelloworld.txt";
String newline = null;

// constructor
//
public JLinkHelloWorld () {
programName = this.getClass().getName();
try {
log = new FileWriter(programName + ".log");
newline = System.getProperty("line.separator");
}
catch (Exception e) {
// couldn't create log file, ignore
}
}

// Display message in Pro/Engineer
//
public void DisplayMessage ( String mesg ) throws Exception {
stringseq seq = stringseq.create();
seq.set(0, mesg);
session.UIDisplayMessage(msgFile, "JLHW %s", seq);
seq.clear();
writeLog(mesg);
}

// Write text to log file
//
public void writeLog ( String mesg ) {
try {
if (log == null) { return; }
log.write(mesg + newline);
log.flush();
}
catch (Exception e) {
// ignore
}
}

// Close log file
//
public void closeLog () {
try {
if (log == null) { return; }
log.close();
}
catch (Exception e) {
// ignore
}
}

// Called by Pro/Engineer when starting the application
//
public static void startApp () {
try {
App = new JLinkHelloWorld();
App.startupApplication();
}
catch (Exception e) {
App.writeLog("Problem running startupApplication method" + e.toString());
return;
}
}

// Called by Pro/Engineer when stopping the application
//
public static void stopApp () {
try {
App.shutdownApplication();
}
catch (Exception e) {
App.writeLog("Problem running shutdownApplication method" + e.toString());
return;
}
}

// Perform some steps when shutting down the application
//
public void shutdownApplication () throws Exception {
writeLog("Application '" + programName + "' stopped");
closeLog();
}

// Perform some steps when starting the application
//
public void startupApplication () throws Exception {

try {
writeLog("Application '" + programName + "' started.");
session = pfcGlobal.GetProESession();
}
catch (jxthrowable x) {
writeLog("ERROR: Problem getting session object.");
return;
}

UICommand btn1_cmd = null;

try {
// Define a UI command
btn1_cmd = session.UICreateCommand(
"JLHW Btn1 Cmd", new JLHW_Btn1_CmdListener()
);
}
catch (jxthrowable x) {
writeLog("ERROR: Problem creating uicmd.");
return;
}

try {
// Add UI command to 'Tools' menu
session.UIAddButton(
btn1_cmd, "Utilities", null,
"JLHW Btn1 Label", "JLHW Btn1 Help",
"msg_jlinkhelloworld.txt"
);
}
catch (jxthrowable x) {
writeLog("ERROR: Problem creating menu: " + x.toString());
return;
}

DisplayMessage(programName + " application started.");

}

// Callback for the 'Tools' menu button
//
public void Btn1_callback ( ) throws Exception {

String mesg = null;
Model model = session.GetCurrentModel();

if (model == null) {
mesg = "Hello!";
}
else {
mesg = "Hello! The model is: " + model.GetFileName();
}

DisplayMessage(mesg);

}

// Inner class for UI Command Listener
//
public class JLHW_Btn1_CmdListener extends DefaultUICommandActionListener {

// Handler for button push
//
public void OnCommand () {
try {
Btn1_callback();
}
catch (Exception e) {
writeLog("Exception thrown by Btn1_callback method: " + e.toString());
}
}

}

}
 


As you can see, there are a number of non-trivial steps involved in creating J-Link applications. However, creating simple programs is really easy.

If you need a J-Link program written for you, please contact me at MarcMettes@InversionConsulting.com to discuss your requirements.

Monday, June 8, 2009

Java: JOptionPane Examples Part 3 - Advanced

Using Event Listeners and JPanels with JOptionPane Dialogs

In my JOptionPane Examples Part 1 and Part 2, I covered many ways to use the JOptionPane, some ordinary, some surprising. Now you may be thinking that the JOptionPane is great, but that you need a level of interactivity that is cannot support. Well, think again.

I was recently asked about Example 3 (from Part 2), which shows the use of JRadioButtons (tied together with a ButtonGroup) and a JTextField. I was asked if the JTextField could have the keyboard focus by default, so that the user could immediately start typing in the JTextField, accepting the default JRadioButton.

Since the JOptionPane tends to take control of the dialog once it starts, a more subtle approach is required. One way to achieve this is to use an event listener. By adding the appropriate event listener to the JTextField object, an action can be performed after the dialog has initialized.


Example 1: Adding Event Listeners to a JTextField

This is an enhancement to the code example in Part 2, Example 3. In order to force keyboard focus into the JTextField, the use of event listeners is required. Also the array argument to showConfirmDialog() is replaced with a JPanel that will be populated with all needed components.

This example requires the following imports in addition to the imports from Part 2:

    import java.awt.event.*;
import javax.swing.event.*;
 

As in the previous version, a couple of labels, some JRadioButtons, a ButtonGroup, and a JTextField are defined.

    JLabel option_label = new JLabel("Select an option:");

JRadioButton b1 = new JRadioButton("Option 1");
JRadioButton b2 = new JRadioButton("Option 2");
JRadioButton b3 = new JRadioButton("Option 3");
b1.setSelected(true);

ButtonGroup group = new ButtonGroup();
group.add(b1);
group.add(b2);
group.add(b3);

JLabel name_label = new JLabel("Enter a name:");

JTextField name = new JTextField(30);
 

In this version, two listeners are added, a ComponentListener and an AncestorListener. Both of these attempt to perform a requestFocus(), when the event is triggered.

Why are both needed? The ComponentListener approach seems to work great until Java 6.0. Unfortunately, Java 6.0 seems to have broken it, which is why the AncestorListener is used as well. Also unfortunately, the AncestorListener approach doesn't work in JVMs prior to 6.0. Fortunately, both approaches can exist together, if you need a solution for both sets of JVMs.

Adding the ComponentListener to the JTextField component (for JVMs prior to Java 6.0):

    name.addComponentListener(new ComponentListener() {
public void componentHidden(ComponentEvent ce) {}
public void componentMoved(ComponentEvent ce) {
ce.getComponent().requestFocus();
}
public void componentResized(ComponentEvent ce) {
ce.getComponent().requestFocus();
}
public void componentShown(ComponentEvent ce) {}
});
 

Adding the AncestorListener to the JTextField component (for Java 6.0 JVM):

    name.addAncestorListener(new AncestorListener(){   
public void ancestorAdded(AncestorEvent ae){
ae.getComponent().requestFocus();
}
public void ancestorRemoved(AncestorEvent ae){}
public void ancestorMoved(AncestorEvent ae){}
});
 

Now the JPanel is defined and populated. A BoxLayout is used to position the components vertically. The components are placed in the order that they are added to the panel, which is done by using the add() method.

    JPanel panel = new JPanel();
panel.setLayout(new BoxLayout(panel,BoxLayout.Y_AXIS));
panel.add(option_label);
panel.add(b1);
panel.add(b2);
panel.add(b3);
panel.add(name_label);
panel.add(name);
 

The only other change to the previous version is that the second argument to showConfirmDialog() is changed from an array to the JPanel, which is called panel.

    int res = JOptionPane.showConfirmDialog(null, panel, "Select", 
JOptionPane.OK_CANCEL_OPTION);
 

The existing result processing code can be used unchanged and therefore will not be duplicated here.


Example 2: Adding an Event Listener to a JPasswordField

Another example that could use an event listener is the JPasswordField. In Example 1 (Part 2), I discussed creating a login/password dialog box using JOptionPane. In this example, the previous code will be enhanced to demonstrate dynamic use of the setEchoChar() method.

Lotus Notes has had a curious password entry feature for a long time. When entering a password, the password masking character changes with each key press. The value of this is debatable, but reproducing this feature presents an interesting challenge. This phenomenon can be achieved in Java by using a KeyListener.

This example requires the following imports in addition to the imports from Part 2:
    import java.awt.event.*;
 

The code for defining the dialog components, is identical.

    JLabel label_loginname = new JLabel("Enter your login name:");
JTextField loginname = new JTextField(15);

JLabel label_password = new JLabel("Enter your password:");
JPasswordField password = new JPasswordField();

JCheckBox rememberCB = new JCheckBox("Remember me");
 

Now a KeyListener is added to the password field. First a list of characters to use for the password masking is defined as a char array. Then each of the KeyListener methods must be defined, even if they are not used.

The keyTyped() method is using for the activity. First the source component is extracted using the getSource() method. Then the setEchoChar() method of the JPasswordField is called. A random char from the char array is passed to setEchoChar() as its only argument.

    password.addKeyListener(new KeyListener() {
char[] mask = { '@', '#', '$', '%', '&', '*' };
public void keyPressed(java.awt.event.KeyEvent ke) {}
public void keyReleased(java.awt.event.KeyEvent ke) {}
public void keyTyped(java.awt.event.KeyEvent ke) {
JPasswordField source = (JPasswordField)ke.getSource();
source.setEchoChar(mask[(int)(mask.length*Math.random())]);
}
});
 

As each character is typed into the password field, that event is fired, which is caught by the listener. The listener then executes and changes the masking character using setEchoChar().

The array definition and call to showConfirmDialog() are repeated here for reference, but are unchanged from the original example.

    Object[] array = { label_loginname, 
loginname,
label_password,
password,
rememberCB };


int res = JOptionPane.showConfirmDialog(null, array, "Login",
JOptionPane.OK_CANCEL_OPTION,
JOptionPane.PLAIN_MESSAGE);
 


As you can see there are many ways to add interactivity to JOptionPane dialogs.

Friday, June 5, 2009

J-Link: Getting Started with Java and Pro/Engineer, Part 1

J-Link can seem intimidating at first, but once you have all the little pieces in hand to get started, it's actually pretty simple. In this article, I'll walk you through all the pieces that you need to create a basic J-Link application.

Even a simplistic J-Link program requires several files, including your Java code compiled to class file(s), a text-based "message file", and an auxiliary application "registry file". I'll explain the required content of each file and how to put them all together.

The Registry File

The registry file is used to instruct Pro/Engineer on how to load an auxiliary application, such as a Pro/Toolkit or J-Link program. Pro/Engineer uses this to find all of the program's pieces. The actual name of the file is not that important, but "protk.dat" will be used as the filename in this example.

An auxiliary application can be started by Pro/Engineer automatically by using the PROTKDAT config.pro option, or by placing the registry file in certain special locations (i.e. the working directory, within the Pro/Engineer install, etc). Auxiliary applications can also be started manually.

Here is an example registry file for the J-Link "Hello World" application:

name               JLinkHelloWorld-App
startup java
java_app_class JLinkHelloWorld
java_app_start startApp
java_app_stop stopApp
allow_stop true
delay_start false
text_dir M:/apps/JLinkHelloWorld/text
java_app_classpath M:/apps/JLinkHelloWorld
end
 

The "name" will appear in the auxiliary application dialog to aid in identifying the program and can be as descriptive (or cryptic) as you need. The "startup java" entry is required for J-Link apps.

"java_app_class" indentifies the primary class of the application where the "java_app_start" and "java_app_stop" methods are contained. The program is required to have static "stop" and "start" methods matching the names given in the registry file (more on that later).

The "allow_stop" option allows the user to stop the program if it is running. The "delay_start" option (if set to "true") can disable automatic startup allowing the user to do this later, manually, if desired.

The "text_dir" option identifies the folder hierarchy containing your application's message files. Although the inclusion of the "text" folder in the path is not always required, it doesn't cause any problems. Unix style paths work just fine here, as they do with config.pro files. Consult my other articles about cool stuff with registry files.

"java_app_classpath" is generally used to find other class and jar files that you may use in your application. It's like having a private CLASSPATH environment variable for each program. As an alternative, this option can be replaced by using the ADD_JAVA_CLASS_PATH config.pro option, using the same value. However, keeping the option in the registry file is generally the simpler approach.

Not every entry is required, but it's usually a good idea to use the above set for greater flexibility.


The config.pro File

To initiate the program upon startup of Pro/Engineer, you can use a PROTKDAT config.pro option. The value of this option is the full path to the registry file.

Using the above example, the option might look like this:

PROTKDAT M:/apps/JLinkHelloWorld/protk.dat
 

The Text Message File

The text message file is used primarily to support internationalization. However, it also enables "code vs data" separation by relocating static text to standalone files. A text message file is needed for even the most basic J-Link program.

Each "entry" in a text message file is comprised of four lines. Typically only the first two are used, the last two are placeholders. The first line is the "message id" that you use in your code, and the second line is the text that will generally appear for that message id.

In this example, the text message file msg_jlinkhelloworld.txt will contain three entries as follows:

JLHW Btn1 Label
JLink Hello World
#
#
JLHW Btn1 Help
Run the JLink Hello World Application
#
#
JLHW %s
%0s
#
#
 

Be sure to leave an extra blank line at the end of the file to avoid one of the many strange problems with text message files.

The text message file must be placed within the folder structure specified by "text_dir" in the registry file. In this example application, the text message file will be placed in:
    M:/apps/JLinkHelloWorld/text/usascii
 

Proper location of the text message file is very critical. Putting the file in the wrong folder is the source of confusion and frustation to new Pro/Engineer API developers.


Read Part 2 for the Java code

Monday, June 1, 2009

Intralink Scripting: Commonspace Folder Info - Part 3

In Part 1, I discussed how to use the ILFolderTreeModel class to recursively descend through your Commonspace folders to generate a folder list. In Part 2, I discussed how to use CSFolderObjectInfo to perform actions and obtain information on your folders. Now I'm going to discuss yet another data model class to get even more extensive information from your folders.

Intralink classes that extend ILTableModel are the core data presentation classes of the Intralink client. While the ObjectInfo classes represent a chunk of metadata for a specific object, the ILTabelModel presents the data model that is actually displayed in the GUI. Interrogating these classes lets you get data exactly as you would expect to see it in Intralink.

Two folder related data presentation classes that I'll now discuss are ILFolderAuthTableModel and ILPromoteDemoteTableModel. As you might expect from the name, ILFolderAuthTableModel represents the data presentation model of folder authorizations, while ILPromoteDemoteTableModel represents folder release procedures.

To utilize these classes, we need an ILAppObject representation of the folder. The static method getObjectByKey() of the ObjectInfo class allows us to create this object. Here is an example of instantiating an ILFolderAuthTableModel object:

    String folderName = "Root Folder/ProjectX";
ILAppObject fol_ao = ObjectInfo.getObjectByKey(ObjectInfo.tFolder, folderName);
ILFolderAuthTableModel fa_tm = new ILFolderAuthTableModel(false, fol_ao);
 

The table of data contained in the ILTableModel based object can be accessed with generic methods and other methods specific to the data type. Examples of generic methods include getRowCount(), getColumnCount(), getColumnName(), and getValueAt().

Example: Reporting Authorizations and Release Procedures

This example further expands upon the code in Part 2, by adding the output of folder authorizations, release level authorizations, and release procedures associated with each folder.

Just as in the GUI, the example shows output only if something was explicitly set for that folder. If a folder authorizations was set only in a parent folder, that authorization will not appear in the output for subfolders. The same applies to release procedures.

    CSFolderObjectInfo fol_oi = null;
ILAppObject fol_ao = null;
ILFolderAuthTableModel fa_tm = null;
ILPromoteDemoteTableModel pd_tm = null;

for (int i=0; i<folders.size(); i++) {

String folderName = folders.get(i).toString();
System.out.println( " " + (i+1) + ": " + folderName );

fol_oi = (CSFolderObjectInfo)ObjectInfo.createByKey( ObjectInfo.tFolder, folderName );
fol_ao = ObjectInfo.getObjectByKey( ObjectInfo.tFolder, folderName);

System.out.println( " " + "Release Scheme: " + fol_oi.getReleaseScheme() );
System.out.println( " " + "File Space: " + fol_oi.getFilespace() );

fa_tm = new ILFolderAuthTableModel(false, fol_ao);
System.out.println( " " + "Authorizations: ");

if (fa_tm.getRowCount() > 0) {

// output column info
for (int col=0; col<fa_tm.getColumnCount(); col++) {
String colName = fa_tm.getColumnName(col);
if (col == 0) {
System.out.print( " " + " " + colName );
}
else {
System.out.print( " / " + colName );
}
}
System.out.println();

// output row info
for (int row=0; row<fa_tm.getRowCount(); row++) {
for (int col=0; col<fa_tm.getColumnCount(); col++) {
String val = fa_tm.getValueAt(row,col).toString();
if (col == 0) {
System.out.print( " " + " " + val );
}
else {
System.out.print( " / " + val );
}
}
System.out.println();
}
}
else {
System.out.println( " " + " None" );
}

pd_tm = new ILPromoteDemoteTableModel(fol_ao);
System.out.println( " " + "Release Procedures: ");

if (pd_tm.getRowCount() > 0) {

// output column info
for (int col=0; col<pd_tm.getColumnCount(); col++) {
String colName = pd_tm.getColumnName(col);
if (col == 0) {
System.out.print( " " + " " + colName );
}
else {
System.out.print( " / " + colName );
}
}
System.out.println();

// output row info
for (int row=0; row<pd_tm.getRowCount(); row++) {
String rellevel = pd_tm.getValueAt(row,pd_tm.RELLEVEL_COLUMN).toString();
String promote_rp = pd_tm.getValueAt(row,pd_tm.PROMOTE_COLUMN).toString();
String demote_rp = pd_tm.getValueAt(row,pd_tm.DEMOTE_COLUMN).toString();
System.out.println( " " + " " + rellevel + " / " + promote_rp + " / " + demote_rp );
}
}
else {
System.out.println( " " + " None" );
}


pd_tm = null;
fa_tm = null;

fol_oi = null;
fol_ao = null;

System.out.println();
System.out.flush();
}
 

The output should look something like this:

  1: Root Folder/Project X
Release Scheme: ReleaseScheme-ProjectX
File Space: Filespace_ProjectX
Authorizations:
Name / Folder Role / WIP / Detail / Prototype / Manufacturing / Obsolete
*Admin / FolAdmin / Admin / Admin / Admin / Admin / Admin
*Engr / FolEngr / Engr / Engr / Engr / Engr / Engr
*Mgr / FolMgr / Mgr / Mgr / Mgr / Mgr / Mgr
*View / FolView / View / View / View / View / View
Release Procedures:
Name / Release Procedure Promote To / Release Procedure Demote From
WIP / /
Detail / /
Prototype / ProjectX_Mgr / ProjectX_Mgr
Manufacturing / ProjectX_Mgr / ProjectX_Mgr
Obsolete / /
 

As you can see, you can extract very specific information from Intralink quite easily with Java. This information would be very difficult, if not impossible, to extract using the GUI itself. A little bit of Intralink Scripting can often save you lots of time.


Need a program for data mining your Intralink metadata? Migrating from Intralink 3.x to PDM.Link or TeamCenter and need to extract metadata quickly? Contact me at MarcMettes@InversionConsulting.com to get an inexpensive application right now.

Tuesday, May 19, 2009

Intralink Scripting: Commonspace Folder Info - Part 2

In Part 1, I discussed how to use ILFolderTreeModel to recursively descend through your Commonspace folders to generate a folder list. Coupled with other classes, you can generate even more data about your folders.

In a previous article, I discussed the ObjectInfo subclasses and how to use them to get object metadata. In this article, I'll discuss how to use the ObjectInfo subclass pertaining to Commonspace folders called CSFolderObjectInfo.

The CSFolderObjectInfo class contains many methods to interact with Commonspace folders. The ObjectInfo class are extremely powerful as they let you bypass the user interface and talk directly with the data model of the Intralink client. Despite its name, the CSFolderObjectInfo class contains many methods to perform actions upon Commonspace folders, not just to get information about them.

Examples of methods specific to the CSFolderObjectInfo are: getParent(), getFullPath(), setDescription(), createFolder(), deleteFolder(), moveFolder(), getFilespace(), getReleaseLevel(), getReleaseScheme(), setRelScheme(), etc. Not all of these are guaranteed to work as you may hope. Caution must be exercised before experimenting on your production system. Do so at your own risk.

Before you can apply these methods, you need a CSFolderObjectInfo object. One way to instantiate a CSFolderObjectInfo object is with the static method createByKey() in the ObjectInfo class, although sometimes there are other ways. Here is an example, using the static field tFolder to specify the ObjectInfo object type and a String value for the folder name:

    CSFolderObjectInfo fol_oi = null;
fol_oi = (CSFolderObjectInfo)ObjectInfo.createByKey( ObjectInfo.tFolder, "Root Folder/ProjectX" );
 

This is a very generic technique that can be applied to most other ObjectInfo data types, even though there are specific createByKey() methods for specific object types.


Example: Release Scheme, File Space, and Release Levels

This example expands upon the code in Part 1, by adding the output of the Release Scheme name, the File Space used, and the Release Levels associated with each folder. Both getReleaseScheme() and getFilespace() return String values of their respective outputs. getReleaseLevel() returns a String array of Release Levels.

    CSFolderObjectInfo fol_oi = null;
List folders = getFolderList(tm, tree);

for (int i=0; i<folders.size(); i++) {

String folderName = folders.get(i).toString();
System.out.println( " " + (i+1) + ": " + folderName );

fol_oi = (CSFolderObjectInfo)ObjectInfo.createByKey( ObjectInfo.tFolder, folderName );

System.out.println( " " + "Release Scheme: " + fol_oi.getReleaseScheme() );
System.out.println( " " + "File Space: " + fol_oi.getFilespace() );

System.out.println( " " + "Release Levels: ");
String[] fol_rellevels = fol_oi.getReleaseLevel();

// release level array values start at 1, but still has element zero
for (int j=1; j<fol_rellevels.length; j++) {
System.out.println( " " + " " + j + ": " + fol_rellevels[j] );
}

fol_oi = null;

System.out.println(); // blank line for readability
System.out.flush(); // flush to see data in output file immediately
}
 


In the next installment, I'll show yet more information about folders that can be gathered, including folder and role based authorizations.

Monday, May 4, 2009

Java: JOptionPane Examples Part 2 - Intermediate

In my previous JOptionPane article, JOptionPane Examples Part 1 - Basic, I covered several examples of basic usage of the JOptionPane dialog. In this installment, we go beyond basic usage and into some functionality that is documented, but not quite so obviously.

In most examples of the JOptionPane dialog, you'll see a String value used for the "message" argument, which is usually the 2nd argument. In the JOptonPane documentation, the "message" argument is listed generically as type Object in the list of methods.

However, if you read at the beginning where the parameters are discussed, the docs state that "message" can not only be a String, but also any subclassed object of the Component class, which is just about any GUI object you could hope to use! But wait there's more!

Not only can it accept practically any GUI object, but it also can accept an array of GUI objects! If an array is provided, the elements of the array are displayed vertically in the dialog box. This is not a very flexible layout, but it is simple and straightforward.

Example 1: Login/Password Dialog

To create a login/password dialog box, we need more than just the login and password fields, but also label fields so the user knows where to type and what goes there.

Here the two field are defined using a JTextField for the login name and a JPassword field for the password. Each field has a corresponding JLabel. Also included, just for fun, is a "remember me" checkbox.

    // Components related to "login" field
JLabel label_loginname = new JLabel("Enter your login name:");
JTextField loginname = new JTextField(15);
// loginname.setText("EnterLoginNameHere"); // Pre-set some text

// Components related to "password" field
JLabel label_password = new JLabel("Enter your password:");
JPasswordField password = new JPasswordField();
// password.setEchoChar('@'); // Sets @ as masking character
// password.setEchoChar('\000'); // Turns off masking

JCheckBox rememberCB = new JCheckBox("Remember me");
 

The components are wrapped up into an Object array.

    Object[] array = { label_loginname, 
loginname,
label_password,
password,
rememberCB };
 

Then the dialog is initiated using showConfirmDialog(), with "OK" and "Cancel" buttons, and without any icon decorations.

    int res = JOptionPane.showConfirmDialog(null, array, "Login", 
JOptionPane.OK_CANCEL_OPTION,
JOptionPane.PLAIN_MESSAGE);
 

We can respond properly to the user's input, by analyzing the int return value of showConfirmDialog().

    // User hit OK
if (res == JOptionPane.OK_OPTION) { System.out.println( "OK_OPTION" ); }

// User hit CANCEL
if (res == JOptionPane.CANCEL_OPTION) { System.out.println( "CANCEL_OPTION" ); }

// User closed the window without hitting any button
if (res == JOptionPane.CLOSED_OPTION) { System.out.println( "CLOSED_OPTION" ); }
 

Even though, at this point, the dialog is gone, we can still interact with the GUI components. We can determine if the user entered values, regardless if the user hit OK, Cancel, or just closed the window without hitting any buttons, by using the methods of the various objects.

For example, we'll use getText() to get the login name from the "login name" JTextField object. The password is obtained using getPassword() from the "password" JPasswordField object. The state of the "remember me" checkbox can be determined using the isSelected() method.

    // Output data in "login" field, if any
String newloginname = loginname.getText();
System.out.println( "newloginname: " + newloginname );

// Output data in "password" field, if any
String newpassword = new String(password.getPassword());
System.out.println( "newpassword: " + newpassword );

// Output state of "remember me" check box
boolean selectedCB = rememberCB.isSelected();
System.out.println( "selectedCB: " + selectedCB );
 

Example 2: Editable JTextArea with JScrollPane scrollbars

In this example, the showMessageDialog() method of the JOptionPane class is used to display a JTextArea, which is going to be pre-populated with some sample text. The size will be set to show 5 rows and 10 columns. The JTextArea is then set to be editable, so the user can change the data.

    JTextArea area = new JTextArea();
area.setText("line1\nline2\nline3\nline4\nline5\nline6");
area.setRows(5);
area.setColumns(10);
area.setEditable(true);
JScrollPane scrollpane = new JScrollPane(area);
 

Along with a JLabel, the JScrollPane object is added to an Object[] array.

    Object[] array = {
new JLabel("Enter some text:"),
scrollpane,
};
 

The showMessageDialog() method produces a dialog with only an "OK" button and does not return any value.

    JOptionPane.showMessageDialog(null, array, "Text", 
JOptionPane.PLAIN_MESSAGE);
 

The JTextArea can still be interrogated to see if the user changed the value, if desired.

    String newtext = area.getText();
System.out.println( "newtext: " + newtext );
 

Example 3: JRadioButton and ButtonGroup

In this example, a dialog will be created consisting of three JRadioButtons and a JTextField. The radio buttons will be associated with a ButtonGroup, which will cause only one of them to be selected at a time.

Create the button objects and make the first button selected by default.

    JRadioButton b1 = new JRadioButton("Option 1");
JRadioButton b2 = new JRadioButton("Option 2");
JRadioButton b3 = new JRadioButton("Option 3");
b1.setSelected(true);
 

Tie the JRadioButtons together into a group, allowing only one of the group to be selected.

    ButtonGroup group = new ButtonGroup();
group.add(b1);
group.add(b2);
group.add(b3);
 

Define a "name" field as well.

    JTextField name = new JTextField(30);
 

Add the elements together in an array with a few JLabels.

    Object[] array = {
new JLabel("Select an option:"),
b1,
b2,
b3,
new JLabel("Enter a name:"),
name
};
 

Make the dialog appear using showConfirmDialog().

    int res = JOptionPane.showConfirmDialog(null, array, "Select", 
JOptionPane.OK_CANCEL_OPTION);
 

As in the first example, we can analyze the int return value of showConfirmDialog().

    // User hit OK
if (res == JOptionPane.OK_OPTION) { System.out.println( "OK_OPTION" ); }

// User hit CANCEL
if (res == JOptionPane.CANCEL_OPTION) { System.out.println( "CANCEL_OPTION" ); }

// User closed the window without hitting any button
if (res == JOptionPane.CLOSED_OPTION) { System.out.println( "CLOSED_OPTION" ); }
 

The selected button can be determined using the isSelected() method.

    // User selected button 1
if (b1.isSelected()) { System.out.println( "Selected: Option 1" ); }

// User selected button 2
if (b2.isSelected()) { System.out.println( "Selected: Option 2" ); }

// User selected button 3
if (b3.isSelected()) { System.out.println( "Selected: Option 3" ); }
 

The name field can be extracted using getText().

    System.out.println( "name=" + name.getText() );
 

These are just a few examples showing how the JOptionPane class was designed to be very versatile and flexible, allowing for moderately complicated GUI dialog to be created quite easily. Perhaps this usage would have been more obvious if the documentation was clearer, but it is documented.

next time ... part 3 advanced

Monday, April 27, 2009

Intralink Scripting: Commonspace Folder Info, Part 1

A lot of administrative tasks in Intralink 3.x are much more difficult than necessary, and in some cases nearly impossible. Folder information is one of those tasks. Without running the DSMU, getting information on folders can be extremely tedious.

Fortunately with the right Java code, we can put some of the internal features of the Intralink client to use. These are the same data model classes that the client Java code uses, but now we can use them to get the information that you want, how you want it, and whenever you want it. Remember, it's your data.


To traverse the Commonspace folder structure, my utility method getFolderList() will be used. It uses an ILFolderTreeModel object, which is a folder tree data model class, and a TreeNode, which is a node within a tree structure. The method calls itself recursively to traverse a folder tree, which need not start at the Commonspace root folder.

Initially, the method builds a new ArrayList. Then, the root node is added to the ArrayList. The method then iterates over the children, if any, of the current node. If there are child nodes, the method calls itself using the same ILFolderTreeModel object, but this time with the child node. The resulting ArrayList of the child node recursion is added to the higher level ArrayList. The child ArrayList is then cleared and nulled. The first ArrayList is returned when recursion has completed.

  public List getFolderList ( ILFolderTreeModel treemodel, TreeNode tree ) throws Exception {

List list = new ArrayList();
list.add(tree.getKeyName());

for (int i=0; i<treemodel.getChildCount(tree); i++) {

TreeNode child = (TreeNode)treemodel.getChild(tree,i);

List sublist = getFolderList(treemodel, child);
list.addAll(sublist);
sublist.clear();
sublist = null;

}

return list;

}
 

In order to call this function, an ILFolderTreeModel and a TreeNode object are required.

For the ILFolderTreeModel object, we can simply call the constructor:
    ILFolderTreeModel tm = new ILFolderTreeModel();
 

If the root node is the Commonspace Root Folder, then the TreeNode object is obtained as follows from the ILFolderTreeModel object:
    TreeNode tree = (TreeRootNode)tm.getRoot();
 

If another folder is the root for the TreeNode, things get a bit more complicated. First, an ILAppObject is created representing the folder of interest, then TreeObject.createCSTree() is executed using the ILAppObject. Running the getRoot() method of the resulting TreeObject gives us a TreeNode that we can use.
    String startFolderName = "Root Folder/ProjectX";
ILAppObject start_fol_ao = ObjectInfo.getObjectByKey( ObjectInfo.tFolder, startFolderName );
TreeNode tree = TreeObject.createCSTree(start_fol_ao).getRoot();
 

To get the ArrayList, getFolderList() is executed:
    List folders = getFolderList(tm, tree);
 

To iterate over the resulting ArrayList, use code like this:
    for (int i=0; i<folders.size(); i++) {
String folderName = folders.get(i).toString();
System.out.println( " " + (i+1) + ": " + folderName );
}
 

The output will appear in the .proi.log file on Unix or the most recent .pro.log.N file on Windows.


Here is the complete program. Please note the addition of three import statements beyong the typical two that Intralink provides in each Intralink Scripting application.

import com.ptc.intralink.client.script.*;
import com.ptc.intralink.script.*;

import com.ptc.intralink.client.admin.folder.*;
import com.ptc.intralink.ila.*;
import java.util.*;


public class Folder_List extends ILIntralinkScript {

ILIntralinkScriptInterface IL = (ILIntralinkScriptInterface)getScriptInterface();


public void run () throws Exception {

TreeNode tree = null;
ILFolderTreeModel tm = new ILFolderTreeModel();

String startFolderName = null;
startFolderName = "";
startFolderName = "/";
startFolderName = "Root Folder";
startFolderName = "Root Folder/ProjectX";

if ( startFolderName == null || startFolderName.matches("^/?$") ) {
tree = (TreeNode)tm.getRoot(); // Casting to TreeNode required
}
else {
ILAppObject start_fol_ao = ObjectInfo.getObjectByKey( ObjectInfo.tFolder, startFolderName );
tree = TreeObject.createCSTree(start_fol_ao).getRoot();
}

System.out.println( " tree: " + tree );
List folders = getFolderList(tm, tree);

for (int i=0; i<folders.size(); i++) {
String folderName = folders.get(i).toString();
System.out.println( " " + (i+1) + ": " + folderName );
System.out.flush();
}

folders.clear();
folders = null;
tm = null;

}


public List getFolderList ( ILFolderTreeModel treemodel, TreeNode tree ) throws Exception {

List list = new ArrayList();
list.add(tree.getKeyName());

for (int i=0; i<treemodel.getChildCount(tree); i++) {

TreeNode child = (TreeNode)treemodel.getChild(tree,i);

List sublist = getFolderList(treemodel, child);
list.addAll(sublist);
sublist.clear();
sublist = null;

}

return list;

}

}
 


In the next installment, I'll show how more information about folders can be gathered, along with a more sophisticated presentation of the data.

Tuesday, March 31, 2009

Book Review: Head First Java

Head First Java, 2nd Edition by Kathy Sierra and Bert Bates is the introductory Java entry in O'Reilly Media's Head First Series. The approach of this unique series is to inject humor, imagery, basic explanations, and a lot of fun into their subject matter. While not for everyone, the series does provide a solid learning experience.

The ideal reader of Head First Java is someone that has done some programming (maybe even some Java), but is fairly new to Java and/or Object Oriented programming. Advanced Java developers are probably not going to get a lot out of this book, but the topics covered are pretty extensive. The topics include: objects and classes, object oriented design, inheritance, polymorphism, constructors, garbage collection, static classes and methods, swing, multithreading, generics, java web start, and RMI. That may sound like a lot of topics, but each one is explained very effectively.

The Head First Series strives to break topics down into very easy to understand explanations, with numerous code examples and exercises. In fact, complex topics are explained multiple times to cover all facets of the subject matter. You won't become an expert, but you will have a very solid understanding and be able to code simple applications right away. More importantly, you will understand why Java developers uses language features in specific ways.

The authors of Head First Java avoid tool distractions by designing the book around using only the JDK and a simple text editor. No other tools or applications are required to use the examples in the book. The major code examples in the book concern two programs that are incrementaly developed through the chapters. One is a very primitive battleship game, focusing on game design logic, rather than a GUI (there is no GUI). The other is a beatbox midi sequencer, which includes functionality from the Java Sound API, a Swing GUI, and a client/server architecture.


Chapter Summary:

Chapter 1 (Breaking the Surface) discusses basic details on classes, and how to interface with the main() method. Also included is more basic programming details on conditionals, looping and and other flow control mechanisms.

Chapter 2 (A Trip to Objectville) gets into objects, object oriented principles, and the importance of unit testing. Garbage collection is briefly discussed.

Chapter 3 (Know Your Variables) covers variables including primitives and object references. Both primitive and object arrays are mentioned and their limitations discussed. More details on the garbage collection system is detaled. There is also a useful table showing all of Java's reserved keywords.

Chapter 4 (How Objects Behave) mentions more details on classes, methods (parameters vs arguments, return values, and pass-by-value), and encapsulation. Included is a discussion on instance vs. local variables.

Chapter 5 (Extra-Strength Methods) contains a more detailed discussion on object oriented design and unit testing. The new Java 5 enhanced for loop syntax is mentioned along with type casting of primitives.

Chapter 6 (Using the Java Library) dives into Java standard libraries with ArrayLists, importing, and packages. Also mentioned are parameterized types and boolean expressions.

Chapter 7 (Better Living in Objectville) continues the object oriented discussion with inheritance (both what it is and how to use it properly) and polymorphism. Both method overriding and method overloading are covered as well.

Chapter 8 (Serious Polymorphism) covers abstract classes and methods. More details on polymorphism and object casting is discussed. Java's version of multiple inheritance called interfaces is introduced.

Chapter 9 (Life and Death of an Object) details the stack and the heap, and how they impact objects and local variables. The chapter also discusses object constructors and superclass constructors, including the use of overloaded constructors. This is followed by a decription of the scoping rules of local variables and factors affecting object lifetime and when garbage collection kicks in.

Chapter 10 (Numbers Matter) provides details on the static and final, when used with both variables and methods. Also included is a mention of wrapper classes for primitive to object conversion (and vice versa), how this conversion can be automatic with autoboxing in Java5.0+. The discussion continues with number formatting, dates, and calendars. Methods from the Math class are covered briefly at the beginning of the chapter.

Chapter 11 (Risky Behavior) discusses details on exception handling, how to use try/catch/finally blocks to deal with exceptions, and how to declare that your method can throw exceptions. Exception objects are explained including exception inheritance, polymorphism, and how to properly catch different types of exceptions. Development on the MIDI application continues with details on the JavaSound API and MIDI events.

Chapter 12 (A Very Graphic Story) gets into the swing of things with coverage of the Swing GUI library. Included in the discussion is an introduction to frames, buttons, listening to events with event listeners, and drawing graphics with Graphics2D. How to utilize inner classes with event listeners for increased flexibility is also covered.

Chapter 13 (Work on Your Swing) gives examples of the use of a few different swing components and several GUI layout managers. Components discussed include JButton, JTextField, JTextArea, JCheckBox, JList, and JPanel. Detailed descriptions on the use of BorderLayout, FlowLayout, and BoxLayout are also included.

Chapter 14 (Saving Objects) covers saving (and restoring) objects through serialization via Serializable. Included is a discussion of the input and output streams of the Java I/O libraries. The Java I/O File and BufferedReader classes are also mentioned.

Chapter 15 (Make a Connection) has details on sockets, multithreading, and synchronization. The chapter builds an example (client and server) chat program to illustrate use of the Socket class, specifically how to read from and write to a socket. A excellent discussion of multithreading is covered in great detail with example code using the Runnable and Thread classes. Also included are details on the unpredictability of the thread scheduler. The chapter wraps up with solid coverge of concurrencies issues when using multithreading and how to avoid it with synchronization.

Chapter 16 (Data Structures) mentions sorting of collection objects using the Comparable class. The collections discussed include ArrayList, HashSet, TreeSet, and HashMap. The concept of generics is explained in greater detail, along with generic method declaration syntax to support polymorphism. Also included is brief mention on the difference between Sets and Maps.

Chapter 17 (Release Your Code) is all about setting up your application for distribution. This includes details on JAR files, how to structure them, what special files they need to contain, and how executable JAR files work. The importance of using packages is covered as well, along with the command line compiler syntax required. Use of Java Web Start to distribute your application to end users is discussed briefly.

Chapter 18 (Distributed Computing) touches on distributed computing topics such as RMI (Remote Method Invocation), servlets, EJB, and Jini. For RMI, the chapter provides a detailed explanation of how the proxy concept works and a dicussion of the process for setting up the remote implementation, the stubs and skeletons required, and the RMI registry. The concept of servlets and EJB are covered but only very briefly (be sure to read Head First Servlets for extensive converage on servlets). The chapter goes on to discuss Jini in great detail, including the discovery process and self-healing nature of the technology. It covers what you need to make it work, then adds the necessary code to enable Jini in the beat box program, both client and server versions.


If you're new to Java, Head First Java will get you started right away. It's approach utilizing humor and imagery may seem childish at first, but once you get started, you'll realize how effective the Head First Series learning series is.

Other topics in the Head First Series:









Thursday, January 22, 2009

Java: JOptionPane Dialog Examples Part 1 - Basic

Java has a huge amount of functionality for creating GUI's for your applications. Some of the more complex GUI's require lots of code. However, sometimes you just need a plain old, nothing fancy, quick-and-dirty, keep it simple dialog. In these situations, the JOptionPane works really well. Basic JOptionPane dialog examples are the core of this article.

Although it's Java's workhouse dialog, there are a few limitations. It helps to understand those limitations such that it fulfills your expectations. The first limitation (some consider this a feature) is modality. The thread which creates the JOPtionPane dialog will wait until the user closes the dialog. The other limitation is that it cannot be resized. The user won't be able to make the dialog bigger in order to see its contents better.

If these limitations are cramping your style, you'll need to move towards JDialog based dialogs. Otherwise, read on for more JOptionPane information.


The Methods

There are four commonly used static methods for JOPtionPane dialogs:

  • showMessageDialog()
  • showInputDialog()
  • showConfirmDialog()
  • showOptionDialog()

All four methods allow for an icon on the left hand side and one or more buttons along the bottom. Both of these are controllable and behave somewhat differently depending on which method is used.

All three of showMessageDialog(), showConfirmDialog(), and showOptionDialog() are capable of displaying some form of information to the user, but each allows the user to respond in different ways. showMessageDialog() allows for only for a message and an OK button. showConfirmDialog() allows for various combinations of OK, YES, NO, and CANCEL buttons. showOptionDialog() allows you to display buttons containing the text of your choice.

Only showInputDialog() allows for the user to enter arbitrary information either in a text field or with a combo-box style list of values. It provides only OK and CANCEL buttons.


Message Types

All four dialog types have options to display a predefined icon. The following icon types are listed below, each of which is a static field of the JOptionPane class. The actual icons displayed depends upon the java look and feel active at that time.

  • JOptionPane.ERROR_MESSAGE -- red letter x, or a "no entry" sign
  • JOptionPane.INFORMATION_MESSAGE -- letter i in a circle
  • JOptionPane.WARNING_MESSAGE -- excalamation point
  • JOptionPane.QUESTION_MESSAGE -- question mark
  • JOptionPane.PLAIN_MESSAGE -- no standard icon


Option Types

The showConfirmDialog() and showOptionDialog() methods have provisions for an "option type", which are the buttons that will be displayed.

  • JOptionPane.DEFAULT_OPTION -- OK button
  • JOptionPane.YES_NO_OPTION -- YES and NO buttons
  • JOptionPane.YES_NO_CANCEL_OPTION -- YES, NO, and CANCEL buttons
  • JOptionPane.OK_CANCEL_OPTION -- OK and CANCEL buttons


Return Values

Both showConfirmDialog() and showOptionDialog() return an int that you can use to figure out how the user responded to the dialog. Did they hit YES, NO, or OK? Just analyze the value using the list below. Perhaps the user closed the dialog window and did not hit one of your buttons? It's an important piece of logic to add to your program.

  • JOptionPane.YES_OPTION -- YES button
  • JOptionPane.OK_OPTION -- OK button
  • JOptionPane.NO_OPTION -- NO button
  • JOptionPane.CANCEL_OPTION -- CANCEL button
  • JOptionPane.CLOSED_OPTION -- User closed the dialog without using a button

For what it's worth, YES_OPTION and OK_OPTION are actually the same value.


Parent Component

In these examples, null is used for the parent component, which creates a standalone dialog, usually centered on the screen. A parent UI component can be used to center the dialog on that component if that is desireable.


User Specified Icon

Some of the method signatures allow for a user specified icon to be used in the dialog box. Even if a particular message type is requested, if a non-null ImageIcon is supplied, that icon will be used in the dialog. This parameter is always optional. null can be used wherever an icon needs to be specified.

Beyond the scope of this article is placement of the icon file. The presumption of this article is that the icon file is in the working directory of the java process. In a more practical application, you will want to locate the icon file in your jar file or in a specific folder. Each of these approaches has a different set of steps to define and will be discussed in a future article.


Method: showMessageDialog()

The dialog produced by showMessageDialog() is a basic dialog displaying a message to the user. The user will see your message with only an "OK" button to close the dialog. No value is returned.


Example 1a: Dialog displaying "Hello!", with OK button, standard "info" icon, and title "Message".

JOptionPane.showMessageDialog(null, "Hello!");
 

Example 1b: Dialog displaying "Hello!", with OK button, no standard icon, and title "Message Title".

int messageType = JOptionPane.PLAIN_MESSAGE; // no standard icon

JOptionPane.showMessageDialog(null, "Hello!", "Message Title", messageType);
 

Example 1c: Dialog displaying "Hello!", with OK button, non-standard icon from GIF file, and title "Message Title".

int messageType = JOptionPane.PLAIN_MESSAGE; // no standard icon
ImageIcon icon = new ImageIcon("blob.gif", "blob");

JOptionPane.showMessageDialog(null, "Hello!", "Message Title", messageType, icon);
 


Method: showInputDialog()

showInputDialog() prompts the user for a value from a text field or within a dropdown combo-box style component. The method returns a String.


Example 2a: Dialog with empty text field, "Enter value:" prompt string, plus OK and CANCEL buttons

String res = JOptionPane.showInputDialog("Enter value:");
System.out.println( "showInputDialog: " + res );
 

Example 2b: Dialog with text field initialized to "Hello!", "Enter value:" prompt string, plus OK and CANCEL buttons

String res = JOptionPane.showInputDialog("Enter value:", "Hello!");
System.out.println( "showInputDialog: " + res );
 

Example 2c: Dialog with empty text field, "Enter value:" prompt string, plus OK and CANCEL buttons

String res = JOptionPane.showInputDialog(null, "Enter value:");
System.out.println( "showInputDialog: " + res );
 

Example 2d: Dialog with text field initialized to "Hello!", "Enter value:" prompt string, plus OK and CANCEL buttons

String res = JOptionPane.showInputDialog(null, "Enter value:", "Hello!");
System.out.println( "showInputDialog: " + res );
 

Example 2e: Empty text field, "Enter value:" prompt string, plus OK and CANCEL buttons

int messageType = JOptionPane.PLAIN_MESSAGE; // no standard icon

String res = JOptionPane.showInputDialog(null, "Enter value:", "Message Title", messageType);
System.out.println( "showInputDialog: " + res );
 

Example 2f: Combo box displaying values from selValues array with value initialized to selValues[0], "Enter value:" prompt string, plus OK and CANCEL buttons

Object[] selValues = { "abc", "def", "ghi" };
int messageType = JOptionPane.PLAIN_MESSAGE; // no standard icon
ImageIcon icon = new ImageIcon("blob.gif", "blob");

Object res = JOptionPane.showInputDialog(null, "Enter value:", "Message Title",
messageType, icon, selValues, selValues[0]);

System.out.println( "showInputDialog: " + res );
 


Method: showConfirmDialog()

showConfirmDialog() produces a dialog similar to that from showMessageDialog(), except that it provides more options for buttons. Button combinations available are OK, YES+NO, YES+NO+CANCEL, and OK+CANCEL. The int return value indicates the button pressed, if any.


Example 3a: Dialog displaying "Hello!", with YES+NO+CANCEL buttons, standard "question mark" icon, and title "Select an Option".

int res = JOptionPane.showConfirmDialog(null, "Hello!");
 

Example 3b: Dialog displaying "Hello!", with YES+NO+CANCEL buttons, standard "question mark" icon, and title "Message Title".

int optionType = JOptionPane.YES_NO_CANCEL_OPTION; // YES+NO+CANCEL
int res = JOptionPane.showConfirmDialog(null, "Hello!", "Message Title", optionType);
 

Example 3c: Dialog displaying "Hello!", with YES+NO+CANCEL buttons, no standard icon, and title "Message Title".

int optionType = JOptionPane.YES_NO_CANCEL_OPTION; // YES+NO+CANCEL
int messageType = JOptionPane.PLAIN_MESSAGE; // no standard icon
int res = JOptionPane.showConfirmDialog(null, "Hello!", "Message Title",
optionType, messageType);
 

Example 3d: Dialog displaying "Hello!", with YES+NO+CANCEL buttons, "blob" icon, and title "Message Title". XXX

int optionType = JOptionPane.YES_NO_CANCEL_OPTION; // YES+NO+CANCEL
int messageType = JOptionPane.PLAIN_MESSAGE; // no standard icon
ImageIcon icon = new ImageIcon("blob.gif", "blob");
int res = JOptionPane.showConfirmDialog(null, "Hello!", "Message Title",
optionType, messageType, icon);
 

Example 3e: Handling Return Values of showConfirmDialog()

The int value returned by showConfirmDialog() tells you which button the users pressed, or if they simply closed the dialog without using any buttons at all.

// User hit YES
if (res == JOptionPane.YES_OPTION) { System.out.println( "YES_OPTION" ); }

// User hit OK
if (res == JOptionPane.OK_OPTION) { System.out.println( "OK_OPTION" ); }

// User hit NO
if (res == JOptionPane.NO_OPTION) { System.out.println( "NO_OPTION" ); }

// User hit CANCEL
if (res == JOptionPane.CANCEL_OPTION) { System.out.println( "CANCEL_OPTION" ); }

// User closed the window without hitting any button
if (res == JOptionPane.CLOSED_OPTION) { System.out.println( "CLOSED_OPTION" ); }
 


Method: showOptionDialog()

showOptionDialog() displays buttons whose labels are based on the values in the input array, with one of the buttons being the default option. If one of the buttons is selected, showOptionDialog() returns the array index number of the selected button.

If the dialog is closed without selecting a button, showOptionDialog() returns -1. It seems to ignore the "option type" parameter.

Example 4a: Dialog displaying "Hello!", with button labels from selValues array, "blob" icon, and title "Message Title".

int optionType = JOptionPane.DEFAULT_OPTION;
int messageType = JOptionPane.PLAIN_MESSAGE; // no standard icon
ImageIcon icon = new ImageIcon("blob.gif", "blob");
Object[] selValues = { "abc", "def", "ghi" };

// Shows message, choices appear as buttons
int res = JOptionPane.showOptionDialog(null, "Hello!", "Message Title",
optionType, messageType, icon,
selValues, selValues[0]);
 

Example 4b: Handling Return Values of showOptionDialog()

System.out.println( "showOptionDialog: " + res);

if (res >= 0) {
System.out.println( "showOptionDialog: " + res + " (" + selValues[res] + ")" );
}

if (res == JOptionPane.CLOSED_OPTION) { System.out.println( "CLOSED_OPTION" ); }
 



These are the basic uses of the JOptionPane. As you can see here, for a simple dialog, there are numerous options, and I have only described a fraction of it's capabilities.

In Part 2, I'll discuss some poorly documented features of JOptionPane, but they may just blow your mind.

Monday, April 28, 2008

J-Link: Compile Your Pro/ENGINEER Java Application with Intralink!

If you've wanted to try some Pro/ENGINEER programming using J-Link (the free Java API), but didn't know how to compile your program, then this article is for you. Forget Netbeans, forget Eclipse, and forget downloading and installing the JDK. Just use Intralink's Java Runtime Environment to compile your apps.

Seriously though, those IDE's are really great programs, but sometimes you can't install them, or they're not available on your system, or you just need something minimal and fast. Fortunately, Intralink contains everything we need to compile J-Link applications: a JRE and tools.jar. tools.jar is the codebase from Javasoft that contains Java compilation code.

Here is a batch file that will compile J-Link Java code on Windows:

@echo off
rem jlink_javac.bat - Compile JLink Apps using Intralink JDK

set Proe_Install_Dir=c:\ptc\proeWildfire2.0_m200
set IL_Install_Dir=c:\ptc\proiclient3.3_m021
set CLASSPATH=%IL_Install_Dir%\i486_nt\lib\tools.jar
set path=%IL_Install_Dir%\i486_nt\jre\bin;%path%

java sun.tools.javac.Main -classpath %Proe_Install_Dir%\text\java\pfc.jar -d . %*
 
Run it like this:

jlink_javac.bat Abc.java Def.java Ghi.java
 

Here is a C shell script that will compile J-Link Java code on Unix:

#!/bin/csh
# jlink_javac.csh - Compile JLink Apps using Intralink JDK

setenv Proe_Install_Dir /opt/proeWildfire2.0_m200
setenv IL_Install_Dir /opt/proiclient3.3_m021
setenv CLASSPATH $IL_Install_Dir/sun4_solaris/lib/tools.jar
set path=( $IL_Install_Dir/sun4_solaris/jre/bin $path )

java sun.tools.javac.Main -classpath $Proe_Install_Dir/text/java/pfc.jar -d . $*
 
Run it like this:

jlink_javac.csh Abc.java Def.java Ghi.java
 

The resulting class files will be in the current directory. While I do use this for my own J-Link code which is typically targeted at Java 1.4.2, it may not work with newer Java versions, nor take advantage of their full capabilities. However, for you command line junkies out there, this is just the recipe.

Tuesday, April 15, 2008

Intralink Scripting: Creating Pro/ENGINEER Trail Files in Java

If you're trying to automate some Pro/ENGINEER activity, the use of trail files is a solid and reliable method. Whether you are trying to automate interference checks, convert files to ProductView or PDF, or just verify a large number of family tables, trail files work great. This is assuming that you don't need any interaction between Intralink and Pro/ENGINEER, during the trail file execution.

The first thing you need is a trail file template. This is simply a trail file that you have recorded. The content will be changed slightly to include variable names where real values will be substituted at runtime.

For example, this:
~ Activate `main_dlg_cur` `ProCmdModelOpen.file`
~ Select `file_open` `Ph_list.Filelist` \
1 `abc.prt`

Becomes this:
~ Activate `main_dlg_cur` `ProCmdModelOpen.file`
~ Select `file_open` `Ph_list.Filelist` \
1 `@NAME@`


One thing to note about trail files, they contain a lot of "fluff", which are lines that you don't need, but got recorded. You won't know what you need and don't need until you try to remove it. My advice is to comment lines you think you don't need, by placing an exclamation point as the first character of the line. Pro/ENGINEER will ignore these lines. If the trail file plays as expected, the line is probably not needed.

This means that any line starting with an exclamation point can be eliminated. The one exception is the first line of the file. You need this, don't remove it. Generally, I leave some of the commented lines that were originally recorded in order to remind myself when an action has started or finished.

There is a config.pro option that will help prevent trail file fluff. Set CMDMGR_TRAIL_OUTPUT to YES and Pro/ENGINEER will reduce the number of lines and increase readability substantially.

Here is an example of a trail file template that records a timestamp, opens a file, records another timestamp, and then exits. You'll notice the comments indicating "blocks" of actions.


!trail file version No. 1301
!!!
!!! Timestamp
!!!
~ Select `main_dlg_cur` `MenuBar1` \
1 `Info`
~ Select `main_dlg_cur` `Info.cb_info_session`
~ Close `main_dlg_cur` `MenuBar1`
~ Close `main_dlg_cur` `Info.cb_info_session`
~ Activate `main_dlg_cur` `psh_util_time`
!!!
!!! Open model
!!!
~ Activate `main_dlg_cur` `ProCmdModelOpen.file`
~ Select `file_open` `Ph_list.Filelist` \
1 `@NAME@`
~ Activate `file_open` `Open`
!Command ProCmdModelOpenExe was pushed from the software.
!!!
!!! Timestamp
!!!
~ Select `main_dlg_cur` `MenuBar1` \
1 `Info`
~ Select `main_dlg_cur` `Info.cb_info_session`
~ Close `main_dlg_cur` `MenuBar1`
~ Close `main_dlg_cur` `Info.cb_info_session`
~ Activate `main_dlg_cur` `psh_util_time`
!!!
!!! Exit
!!!
~ Select `main_dlg_cur` `MenuBar1` \
1 `File`
~ Close `main_dlg_cur` `MenuBar1`
~ Activate `main_dlg_cur` `File.psh_exit`
! Message Dialog: Warning
! : Do you really want to exit?
~ FocusIn `UI Message Dialog` `no`
~ FocusIn `UI Message Dialog` `yes`
~ Activate `UI Message Dialog` `yes`
!End of Trail File

Now that we have a template, we need some java code to create the trail file that Pro/ENGINEER will run. There are many Java templating libraries that do some very complex manipulations, but for this simple example, we need very simple substitutions. The code listed below is quite simple, but works great for basic trail file creation.

Once variables have been established, the first task is to generate file handle objects for reading (using the BufferedReader/FileReader classes) and for writing (using the FileWriter class). Each line of the template file is read from using readLine() in the while loop. Then the value of the objName variable is substituted where @NAME@ is found in the template. The resulting line is written to the output file. Closing both the input and output file is very important, especially on Windows, to make them fully accessible.

String text;
String inputFile = "trl-template.txt";
String outputFile = "trl-output.txt";
String newline = System.getProperty("line.separator");

// Set up input and output files
BufferedReader templateInput = new BufferedReader(new FileReader(inputFile));
FileWriter trailOutput = new FileWriter(outputFile);

// Read through all lines in file
while ((text = templateInput.readLine()) != null) {

// Replace @NAME@ with objName String
text = text.replaceAll( "@NAME@", objName );

// Output modified template line to given output file stream
trailOutput.write(text + newline);

}

trailOutput.close();
templateInput.close();

Following best practices, the code should be placed in its own function, or, even better, its own class. Also, using an array or HashMap, instead of a simple String object for input, would allow for more extensive substitutions to occur during trail file generation, but the above is the core of a simple approach that works very well.

Thursday, April 10, 2008

Intralink Scripting: Working with Dates in Locate

When recording dates during a Locate using the Scripting Options dialog, Intralink records it as a reference to the number of milliseconds since the beginning of 1970.

IL.addFilter( "Created On", ">=", new Object[]{new java.util.Date(1104555600000L)} ); 
It works fine, but with milliseconds it's a little hard to change to a specific date/time. It's still workable if your dates are relative to now, for example, models that have changed in the last 24 hours.

Date onedayago = new Date( (new Date()).getTime() - 24*3600*1000 );
IL.addFilter( "Created On", ">=", new Object[]{onedayago} );
Creating a new Date object sets it to the date/time right now and the getTime() method returns the number of milliseconds since 1970 for that object. For our "24 hours ago" Date object, we just need to subtract 24*3600*1000 milliseconds from that value and use it to create our object.

This is just quick and dirty calculations, if you need more precise calculations, you'll need to turn to Calendar based classes.


If we need to specify absolute date/time values, we'll have to incorporate the GregorianCalendar class into the mix. This class is the "official" way to specify absolute dates, provided you follow the Gregorian calendar.

The first step is to create the two objects and assign date/time values. Here were are setting year, month, day, hour, minute, and second values, but there are other options, such as add(), roll(), and setTime():
GregorianCalendar cal1 = new GregorianCalendar();
GregorianCalendar cal2 = new GregorianCalendar();
cal1.set(2008,0,1,0,0,0); // 2008-01-01 00:00:00
cal2.set(2008,2,15,23,59,0); // 2008-03-15 23:59:00

For the addFilter() method, we'll use the getTime() method on each GregorianCalendar object, which returns a Date object:
IL.addFilter( "Created On", ">=", new Object[]{ cal1.getTime() }
IL.addFilter( "Created On", "<=", new Object[]{ cal2.getTime() } ););

Sunday, April 6, 2008

Intralink Scripting: To Be or Not To Be, How to Best Answer the Question

Checking for Object Existence with CSPIObjectInfo and CSPIVObjectInfo

When processing Intralink objects in Java from a non-Intralink source, such as a text file or database, the typical process is to use Locate to search for the item to ensure that it exists. While this is usually sufficient, there are times when the overheard of using Locate is undesirable. The good news is that there is an alternative using the ObjectInfo derived classes.

Continuing on my discussion of ObjectInfo classes, I'll discuss how to apply these classes to determine whether a PI (i.e. abc.prt) or a PIV (i.e. abc.prt/main/A/0) exists.

If we're checking for "my_assembly.asm", we'd follow these steps. First declare an object of type CSPIObjectInfo. Then attempt to create the object using the createByKey() method of the ObjectInfo class, providing the type as ObjectInfo.tPI and name in the variable piName.

Although I have it captured in a try{} block, it won't throw an exception if the PI doesn't exist, instead as you may have guessed from the code, in merely returns null. If the object is null, it doesn't exist.

String piName = "my_assembly.asm";
CSObjectInfo pi_oi = null;

try {
pi_oi = (CSObjectInfo)ObjectInfo.createByKey( ObjectInfo.tPI, piName );
if (pi_oi == null) {
System.out.println( "pi_oi is null" );
}
else {
System.out.println( "pi_oi getName(): " + pi_oi.getName() );
}
}
catch (Exception e) {
System.out.println( "CSObjectInfo createByKey exception: " + e.getMessage() );
}

To determine is a PIV exists (i.e. "my_assembly.asm", main branch, revision 1, version 0), ObjectInfo.tPIV is the corresponding type to use also with the createByKey() method of the ObjectInfo class. It also has the same behavior, returning null is the PIV does not exist.
String pivName = "my_assembly.asm/main/1/0";
CSPIVObjectInfo piv_oi = null;

try {
piv_oi = (CSPIVObjectInfo)ObjectInfo.createByKey( ObjectInfo.tPIV, pivName );
if (piv_oi == null) {
System.out.println( "piv_oi is null" );
}
else {
System.out.println( "piv_oi getName(): " + piv_oi.getName() );
}
}
catch (Exception e) {
System.out.println( "CSPIVObjectInfo createByKey exception: " + e.getMessage() );
}

The major weakness with this approach for PIV's is that you have to know of it's name, revision, and version ahead of time. If you want to know what the latest revision/version is, the Locate approach will give you both confirmation of existence and knowledge of the revision/version at the same time.

You'll have to choose when it makes sense to use it, but it remains a very powerful technique nonetheless.

Friday, April 4, 2008

Intralink Scripting: Font Size Does Matter!

Using Java Swing Utilities to make the Intralink GUI text more readable

The font in the Intralink client is a little small for most people, and as a result changing the font size is a question that pops up from time to time. Unfortunately, Intralink doesn't provide a way to do this directly, which makes most people just give up.

Since we're dealing with Java, almost anything is possible, and changing the font is one of those things. There's basically three main steps: 1) Setup the font object to use, 2) Tell the UIManager is use the new font details, 3) Force a full GUI redraw. It's easiest to do this when Intralink is starting, then the user is good to go from that point on.

Steps 1 & 2 are encompassed into one function setMenuFontSizeDefault(). The first task is to obtain the font object used for menus ("Menu.font"). Then the details (Name, Style, Size) are extracted. The function is changing only the size, but a new font could be used as well (check the comments). With the changed font details, a new FontUIResource object is created.

The next task is to associate the new font object with many different font attributes, obviously in what looks to be a brute force attack.

private void setMenuFontSizeDefault(int size) {

// get font style and name from current menu font
//
String BaseFontSource = "Menu.font";
Font fontCurrent = UIManager.getFont(BaseFontSource);
String name = fontCurrent.getName();
int style = fontCurrent.getStyle();

String styleName = "(?)";
if (style == Font.PLAIN) { styleName = "(Plain)"; }
if (style == Font.ITALIC) { styleName = "(Italic)"; }
if (style == Font.BOLD) { styleName = "(Bold)"; }

System.out.println( "Current " + BaseFontSource + " name/style/size: "
+ name + "/" + style + styleName + "/" + fontCurrent.getSize() );

// Override font name and/or style by uncommenting one of these
// name = "Times New Roman";
// style = Font.BOLD; styleName = "(Bold)";

System.out.println( "Changing name/style/size to: " + name + "/" + style + styleName + "/" + size );

// create similar font with the specified size
//
FontUIResource fontResourceNew = new FontUIResource(name, style, size);


// change UI defaults for all types of menu components
//
UIManager.put("Button.font", fontResourceNew);
UIManager.put("CheckBox.font", fontResourceNew);
UIManager.put("CheckBoxMenuItem.acceleratorFont", fontResourceNew);
UIManager.put("CheckBoxMenuItem.font", fontResourceNew);
UIManager.put("ComboBox.font", fontResourceNew);
UIManager.put("DesktopIcon.font", fontResourceNew);
UIManager.put("EditorPane.font", fontResourceNew);
UIManager.put("FormattedTextField.font", fontResourceNew);
UIManager.put("InternalFrame.titleFont", fontResourceNew);
UIManager.put("Label.font", fontResourceNew);
UIManager.put("List.font", fontResourceNew);
UIManager.put("Menu.acceleratorFont", fontResourceNew);
UIManager.put("Menu.font", fontResourceNew);
UIManager.put("MenuBar.font", fontResourceNew);
UIManager.put("MenuItem.acceleratorFont", fontResourceNew);
UIManager.put("MenuItem.font", fontResourceNew);
UIManager.put("OptionPane.buttonFont", fontResourceNew);
UIManager.put("OptionPane.messageFont", fontResourceNew);
UIManager.put("PasswordField.font", fontResourceNew);
UIManager.put("PopupMenu.font", fontResourceNew);
UIManager.put("ProgressBar.font", fontResourceNew);
UIManager.put("RadioButton.font", fontResourceNew);
UIManager.put("RadioButtonMenuItem.acceleratorFont", fontResourceNew);
UIManager.put("RadioButtonMenuItem.font", fontResourceNew);
UIManager.put("Spinner.font", fontResourceNew);
UIManager.put("TabbedPane.font", fontResourceNew);
UIManager.put("Table.font", fontResourceNew);
UIManager.put("TableHeader.font", fontResourceNew);
UIManager.put("TextArea.font", fontResourceNew);
UIManager.put("TextField.font", fontResourceNew);
UIManager.put("TextPane.font", fontResourceNew);
UIManager.put("TitledBorder.font", fontResourceNew);
UIManager.put("ToggleButton.font", fontResourceNew);
UIManager.put("ToolBar.font", fontResourceNew);
UIManager.put("ToolTip.font", fontResourceNew);
UIManager.put("Tree.font", fontResourceNew);
UIManager.put("Viewport.font", fontResourceNew);
UIManager.put("JTitledPanel.title.font", fontResourceNew);

}

Probably a better way, but it works!


Step 3 is accomplished with the updateAllFrames() function. With the frames gathered from Frame.getFrames(), the font change is forced upon the frame (and the frames windows, if any) using SwingUtilities.updateComponentTreeUI().

private void updateAllFrames () {

Frame frames[] = Frame.getFrames();

for (int i = 0; i < frames.length; i++) {

if ( frames[i].getTitle().equals("") frames[i].getTitle().equals("Pending RTP Forms") ) {
continue;
}

// Update the frames
try {
SwingUtilities.updateComponentTreeUI(frames[i]);
}
catch (Exception e) {
// Exception thrown, skip it
}

// Update the frame's windows
Window windows[] = frames[i].getOwnedWindows();
for (int j = 0; j < windows.length; j++) {
try {
SwingUtilities.updateComponentTreeUI(windows[j]);
}
catch (Exception e) {
// Exception thrown, skip it
}
}

}

}

The two function are called in this order, as an example:

setMenuFontSizeDefault(15); // set font size to 15
updateAllFrames();



These import statement might be necessary as well:

import java.util.*;
import java.awt.*;
import javax.swing.*;
import javax.swing.plaf.*;