Contents Previous Next

Advanced Features

JDeveloper's SCM Framework provides several advanced features to help you integrate your source code control extension into the JDeveloper IDE more tightly. These features are all optional, though generally we recommend using them to give users more features and better integration.

Many advanced features relate to the interfaces used in the SCM Framework's Environment API. You can obtain them by using getEnvironment() in the core class oracle.ide.scm.SCMSystem.

This section studies how to:

Implementing a Log Page

JDeveloper's existing source code control support uses a log page that displays details of the commands and other information passed to the system. You can create a log page for the messages passed to your system easily using the SCM Utility Set.

The SCM Framework removes all log windows when it deactivates an extension, so the best time to create your log page is in prepareClient() for your source code control client. Hold a reference to the log writer here, so operations can use the log page to display feedback to the user.

Perform your clean up of the log writer in your client in finishClient():

import oracle.ide.scm.SCMClient;
import oracle.ide.scm.SCMSystem;
import oracle.ide.scm.env.SCMUtilitySet;
import oracle.ide.scm.util.SCMLogWriter;
import oracle.ide.scm.error.SCMException;


public class ACMEVCSClient implements Addin, SCMClient
{
  private static SCMLogWriter s_writer;

  ...
  public void prepareClient() throws SCMException
  {
    SCMUtilitySet utils = SCMSystem.getEnvironment().getUtils();

    s_writer = utils.createLogger( "Acme VCS" );
  }

  public void finishClient() throws SCMException
  {
    s_writer.close();
    s_writer = null;
  }

  public static SCMLogWriter getWriter()
  {
    return s_writer;
  }
  ...
}

Hint: We recommend calling close() on SCMLogWriter after writing your messages to the log page. This releases any associated resources and ensures a valid output stream to the log page is obtained when required later.

Integrating with Core JDeveloper IDE Features

Parts of the JDeveloper IDE use the interface oracle.ide.scm.SCMFileSystem to interact with source code control systems, e.g. Tools | Refactor.... In future releases, we plan to use similar APIs to provide tighter integration for commands such as File | Rename....

The easiest way to implement SCMFileSystem is to extend oracle.ide.scm.SCMFileSystemAdapter and override the methods you want to implement. In your client, return a new instance of SCMFileSystem from getFileSystem().

Adding Options to Source Code Control Preferences

You may find it worthwhile to enable users to set general options for the source code control system as necessary, separate to core features actions such as check in or check out. JDeveloper provides these preferences for its existing source code control support in Options | Preferences. In the Preferences dialog there is a node for source code control, in which you can display options specific to your system.

This is possible using the SCM Framework's Properties API. First, implement SCMPropertyCustomizer by extending the adapter class oracle.ide.scm.SCMPropertyCustomizerAdapter.

This class must return the panel in which your preferences will display, and handle the user interface for each property setting. The panel will appear on the right of the preferences dialog when your source code system is selected in the left hand navigator. In setProperties(Map), apply your properties in the given map to your preference panel, and return a new map with the correct property settings for the user interface from getProperties():

import oracle.ide.scm.SCMOptionsCustomizerAdapter;
import oracle.ide.scm.SCMOptions;

public class ACMECheckOutCustomizer extends SCMOptionsCustomizerAdapter
{
  static final Object KEY_CHECKOUT_RESERVED = "acme_optn_reserved";

  private SCMOptions m_options = new SCMOptions();
  private JCheckBox m_checkbox;

  public Component getComponent()
  {
    JPanel panel = new JPanel();
    m_checkbox = new JCheckBox();

    // Build the option customizer's user interface.
    panel.add( m_checkbox );

    return panel;
  }

  public SCMOptions getOptions()
  {
    return m_options;
  }

  public void commitOptions()
  {
    m_options.setGeneralOption(
      KEY_CHECKOUT_RESERVED, new Boolean( m_checkbox.isSelected() )
    );
  }
}

Hint: To ensure validation checks catch incorrect states that may occur, override validateProperties() and throw an exception when those checks identify illegal values.

All your properties should have key constants for lookup purposes. The SCM Framework automatically persists property settings for the next time a user starts JDeveloper, so its persistence mechanism must be able to handle the type of keys and values used. You can safely use String, object wrappers for the fundamental Java types and JavaBeans here.

After implementing SCMPropertyCustomizer, return a new instance of it from getPropertyCustomizer(). To get the current value of a property setting, use oracle.ide.scm.env.SCMProperties obtained through SCMSystem.getEnvironment().getProperties() and call get(Class, String) to find the property's current value, passing in a class reference for your client. You can use the other methods in SCMPropertyCustomizer to install defaults for property values and receive notification when a property is changed:

import oracle.ide.scm.SCMClient;
import oracle.ide.scm.SCMSystem;
import oracle.ide.scm.SCMPropertyCustomizer;
import oracle.ide.scm.env.SCMProperties;

public class ACMEVCSClient implements Addin, SCMClient
{
...
  public void initialize()
  {
    SCMProperties properties = SCMSystem.getEnvironment().getProperties();

    properties.putDefault(
      ACMEVCSClient.class,
      ACMEPropertyCustomizer.KEY_CHECKIN_IDENTICAL,
      Boolean.FALSE
    );
  }

  public SCMPropertyCustomizer getPropertyCustomizer()
  {
    return new ACMEPropertyCustomizer();
  }
...
}

Displaying Dialogs for Source Code Control Actions

The SCM Framework has a predefined dialog for listing files which you can use with your Operations after filtering the selected files as necessary and before you run that operation.

To use this dialog, return an object that implements SCMDialogSpecification from getDialogSpecification() for the file-based operation you want to complete. If you return null from this method, no dialog will display.

oracle.ide.scm.SCMDialogSpecificationObject describes the interface from your client to this dialog. Create an instance of this class and configure the specification of the dialog you want to display. You can set the size, caption, prompt (or hint) and layout style on the specification:

import oracle.ide.scm.SCMDialogSpecification;
import oracle.ide.scm.SCMDialogSpecificationObject;

public class CheckInOperation extends SCMMultiFileOperation
{
...
  public SCMDialogSpecification getDialogSpecification()
  {
    SCMDialogSpecificationObject spec = new SCMDialogSpecificationObject();

    // Set a caption, or title, for the dialog.
    spec.setCaption( "Check In" );

    // Set the hint text to be displayed in the dialog.
    spec.setLongPrompt( "Click OK to check these files in to Acme VCS" );

    // Set label text for the watcher, or progress indicator.
    spec.setWatcherDescription( "Checking in files now..." );

    return spec;
  }
...
}

Customizing Dialog Options

If you want to install a panel into the file list dialog to configure user options, implement the utility SCMOptionsCustomizer for the dialog specification you return from the operation. The easiest way to achieve this is to extend oracle.ide.scm.SCMOptionsCustomizerAdapter.

SCMOptionsCustomizer is similar to SCMPropertyCustomizer because it coordinates the user interface for the options and applies specified data to an underlying model. However, in this case SCMOptionsCustomizer is sensitive to a user changing the files selected and the model is an instance of oracle.ide.scm.SCMOptions, created specially for this purpose and capable of relating different option settings to different files.

Hint: Using SCMOptionsCustomizer is optional and generally we recommend you use setGeneralOption(Object, Object) and getGeneralOption(Object) instead to track the files selected for each option. This enables you to implement global option settings more easily as it avoids 'tri-states' in SCMOptionsCustomizer.


import oracle.ide.scm.SCMOptionsCustomizerAdapter;
import oracle.ide.scm.SCMOptions;


public class ACMECheckOutCustomizer extends SCMOptionsCustomizerAdapter
{
  static final Object KEY_CHECKOUT_RESERVED = "acme_optn_reserved";

  private SCMOptions m_options = new SCMOptions();
  private JCheckBox m_checkbox;

  public Component getComponent()
  {
    JPanel panel = new JPanel();
    m_checkbox = new JCheckBox();

    // Build the option customizer's user interface.
    panel.add( m_checkbox );

    return panel;
  }

  public SCMOptions getOptions()
  {
    return m_options;
  }

  public void commitOptions()
  {
    m_options.setGeneralOption(
      KEY_CHECKOUT_RESERVED, new Boolean( m_checkbox.isSelected() )
    );
  }
}

Implementing a List Checkouts Window

The SCM Framework also provides the ability to organize and display a dockable window of all those files currently checked out from the source code control system. You need to implement the virtual methods of oracle.ide.scm.SCMCheckoutLister to list these files in your extension.

oracle.ide.scm.SCMCheckoutLister contains a class to configure the appearance of the List Checkouts window and the metadata to display the list of files, and a 'worker' class to query which files are checked out. You can display as many columns of information as necessary by supplying the keys and display names for each item.

The 'worker' class is SCMListCheckoutsWorker and it defines listCheckouts(Map, SCMCheckoutList) to search for checked out files. Such operations may take a long time, so this method uses a temporary dedicated thread to enable users to continue working while the list is created. JDeveloper displays the list of checked out files to the user dynamically as SCMListCheckoutsWorker builds the list:

import oracle.ide.scm.SCMCheckoutLister;
import oracle.ide.scm.lsco.SCMListCheckoutsSetup;
import oracle.ide.scm.lsco.SCMListCheckoutsWorker;
import oracle.ide.scm.lsco.SCMListCheckoutsKey;
import oracle.ide.scm.lsco.SCMCheckoutList;
import oracle.ide.scm.error.SCMException;


public class ACMECheckoutLister implements SCMCheckoutLister,
                                           SCMListCheckoutsKey
{
  private static final String KEY_RESULT_USER = "acme_lsco_user";

  public final SCMListCheckoutsSetup getSetup()
  {
    return new CheckoutsSetup();
  }

  public final SCMListCheckoutsWorker getWorker()
  {
    return new CheckoutsWorker();
  }

  private class CheckoutsSetup implements SCMListCheckoutsSetup
  {
    public final Integer getWindowOrientation()
    {
      // Use the default window orientation.
      return null;
    }

    public final String[] getOptionalResultKeys()
    {
      return new String[] { KEY_RESULT_USER };
    }

    public final String getResultName(Object key)
    {
      return (key.equals( KEY_RESULT_USER ) ? "User Name" : "");
    }

    public final String getStatusIdle(Map args)
    {
      return "Finished searching.";
    }
  }

  private class CheckoutsWorker implements SCMListCheckoutsWorker
  {
    public final void listCheckouts(Map args, SCMCheckoutList list)
         throws SCMException
    {
      // Now add details of checked out files to the list
      Map results = new HashMap();

      results.add( KEY_RESULT_FILE, "c:\\dev\\file1.java" );
      results.add( KEY_RESULT_USER, "joebloggs" );

      list.addCheckout( results );
    }
  }
}

You must then implement an operation to return a control item and update the list of checked out files when requested. You can pass arguments to SCMListCheckoutsWorker using refresh(Map) in oracle.ide.scm.env.SCMListCheckouts. This is useful if want users to be able to, e.g. specify a top-level search directory, search on sub-folders or enter other search criteria.

Hint: Override isEnabled(boolean) to prevent invoking the operation when the list checkouts window is still working.


Import oracle.ide.scm.SCMSystem;
import oracle.ide.scm.op.SCMNoFileOperation;
import oracle.ide.scm.error.SCMException;


public class ListCheckoutsOperation extends SCMNoFileOperation
{
...
  public SCMControlItem getControlItem()
  {
    SCMControlItemObject item = new SCMControlItemObject();

    item.setName( "List Checkouts" );
    item.setMnemonic( 'L' );

    return item;
  }

  public Boolean isEnabled(Boolean control)
  {
    return (! SCMSystem.getEnvironment().getListCheckouts().isBusy());
  }

  public void operate() throws SCMException
  {
    Map args = getArgumentsFromUI();
    SCMSystem.getEnvironment().getListCheckouts().refresh( args );
  }

  private final Map getArgumentsFromUI()
  {
    // Obtain any useful arguments from the user here.
    Return new HashMap();
  }
...
}

Finally, the list of checked out files must reflect changes that may occur in your client implementation. Return the class SCMCheckoutLister from getCheckoutLister() and add the new operation to your set of existing operations for your client:

import oracle.ide.scm.SCMClient;
import oracle.ide.scm.SCMCheckoutLister;
import oracle.ide.scm.SCMOperationSet;
import oracle.ide.scm.error.SCMException;

public class ACMEVCSClient implements Addin, SCMClient
{
...
  public SCMOperationSet getOperations()
            throws SCMException
  {
    SCMOperationSet set = new SCMOperationSet();

    set.addCategory(new Class[]
    {
      ListCheckoutsOperation.class
    });
    return set;
  }

  public SCMCheckoutLister getCheckoutLister()
  {
    return new ACMECheckoutLister();
  }
...
}

ACME VCS Sample

The sample file ACMEVCSClient.java demonstrates some of these advanced features.

For more details on ACME VCS, see Introduction.

Back to to

Contents Previous Next