Package nl.lxtreme.ols.client

Source Code of nl.lxtreme.ols.client.ClientController

/*
* OpenBench LogicSniffer / SUMP project
*
* This program is free software; you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation; either version 2 of the License, or (at
* your option) any later version.
*
* This program is distributed in the hope that it will be useful, but
* WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
* General Public License for more details.
*
* You should have received a copy of the GNU General Public License along
* with this program; if not, write to the Free Software Foundation, Inc.,
* 51 Franklin St, Fifth Floor, Boston, MA 02110, USA
*
*
* Copyright (C) 2010-2011 - J.W. Janssen, http://www.lxtreme.nl
*/
package nl.lxtreme.ols.client;


import java.awt.*;
import java.awt.event.*;
import java.io.*;
import java.util.*;
import java.util.List;
import java.util.concurrent.*;
import java.util.logging.*;

import javax.swing.*;

import nl.lxtreme.ols.api.*;
import nl.lxtreme.ols.api.acquisition.*;
import nl.lxtreme.ols.api.data.*;
import nl.lxtreme.ols.api.data.Cursor;
import nl.lxtreme.ols.api.data.annotation.Annotation;
import nl.lxtreme.ols.api.data.annotation.AnnotationListener;
import nl.lxtreme.ols.api.data.export.*;
import nl.lxtreme.ols.api.data.project.*;
import nl.lxtreme.ols.api.devices.*;
import nl.lxtreme.ols.api.tools.*;
import nl.lxtreme.ols.api.ui.*;
import nl.lxtreme.ols.api.util.*;
import nl.lxtreme.ols.client.action.*;
import nl.lxtreme.ols.client.actionmanager.*;
import nl.lxtreme.ols.client.osgi.*;
import nl.lxtreme.ols.client.signaldisplay.*;
import nl.lxtreme.ols.util.*;
import nl.lxtreme.ols.util.swing.*;
import nl.lxtreme.ols.util.swing.component.*;

import org.apache.felix.dm.*;
import org.apache.felix.dm.Component;
import org.osgi.framework.*;
import org.osgi.service.cm.*;


/**
* Denotes a front-end controller for the client.
*/
public final class ClientController implements ActionProvider, AcquisitionProgressListener, AcquisitionStatusListener,
    AcquisitionDataListener, AnnotationListener, ApplicationCallback
{
  // INNER TYPES

  /**
   * Provides a {@link AccumulatingRunnable} that repaints the entire main frame
   * once in a while. This is necessary if a tool produces lots of annotations
   * in a short time-frame, which would otherwise cause the UI to become slow
   * due to the many repaint requests.
   */
  final class AccumulatingRepaintingRunnable extends AccumulatingRunnable<Void>
  {
    /**
     * {@inheritDoc}
     */
    @Override
    protected void run( final Deque<Void> aArgs )
    {
      repaintMainFrame();
    }
  }

  /**
   * Provides an {@link Action} for closing a {@link JOptionPane}.
   */
  static final class CloseOptionPaneAction extends AbstractAction
  {
    private static final long serialVersionUID = 1L;

    /**
     * @see java.awt.event.ActionListener#actionPerformed(java.awt.event.ActionEvent)
     */
    @Override
    public void actionPerformed( final ActionEvent aEvent )
    {
      final JOptionPane optionPane = ( JOptionPane )aEvent.getSource();
      optionPane.setValue( Integer.valueOf( JOptionPane.CLOSED_OPTION ) );
    }
  }

  /**
   * Provides a listener for cursor changes that reflects all changes to their
   * corresponding actions.
   */
  final class CursorActionListener implements ICursorChangeListener
  {
    // METHODS

    /**
     * {@inheritDoc}
     */
    @Override
    public void cursorAdded( final Cursor aCursor )
    {
      updateActionsOnEDT();
    }

    /**
     * {@inheritDoc}
     */
    @Override
    public void cursorChanged( final String aPropertyName, final Cursor aOldCursor, final Cursor aNewCursor )
    {
      // Nothing...
    }

    /**
     * {@inheritDoc}
     */
    @Override
    public void cursorRemoved( final Cursor aOldCursor )
    {
      updateActionsOnEDT();
    }

    /**
     * {@inheritDoc}
     */
    @Override
    public void cursorsInvisible()
    {
      updateActionsOnEDT();
    }

    /**
     * {@inheritDoc}
     */
    @Override
    public void cursorsVisible()
    {
      updateActionsOnEDT();
    }
  }

  /**
   * Provides a default tool context implementation.
   */
  static final class DefaultToolContext implements ToolContext
  {
    // VARIABLES

    private final DataSet dataSet;
    private final int startSampleIdx;
    private final int endSampleIdx;

    // CONSTRUCTORS

    /**
     * Creates a new DefaultToolContext instance.
     *
     * @param aStartSampleIdx
     *          the starting sample index;
     * @param aEndSampleIdx
     *          the ending sample index;
     * @param aData
     *          the acquisition result.
     */
    public DefaultToolContext( final int aStartSampleIdx, final int aEndSampleIdx, final DataSet aDataSet )
    {
      this.startSampleIdx = aStartSampleIdx;
      this.endSampleIdx = aEndSampleIdx;
      this.dataSet = aDataSet;
    }

    /**
     * {@inheritDoc}
     */
    @Override
    public int getChannels()
    {
      return getData().getChannels();
    }

    /**
     * {@inheritDoc}
     */
    @Override
    public Cursor getCursor( final int aIndex )
    {
      return this.dataSet.getCursor( aIndex );
    }

    /**
     * {@inheritDoc}
     */
    @Override
    public AcquisitionResult getData()
    {
      return this.dataSet.getCapturedData();
    }

    /**
     * {@inheritDoc}
     */
    @Override
    public int getEnabledChannels()
    {
      return getData().getEnabledChannels();
    }

    /**
     * {@inheritDoc}
     */
    @Override
    public int getEndSampleIndex()
    {
      return this.endSampleIdx;
    }

    /**
     * {@inheritDoc}
     */
    @Override
    public int getLength()
    {
      return Math.max( 0, this.endSampleIdx - this.startSampleIdx );
    }

    /**
     * {@inheritDoc}
     */
    @Override
    public int getStartSampleIndex()
    {
      return this.startSampleIdx;
    }
  }

  /**
   * A runnable implementation that accumulates several calls to avoid an
   * avalanche of events on the EDT.
   */
  final class ProgressUpdatingRunnable extends AccumulatingRunnable<Integer>
  {
    /**
     * {@inheritDoc}
     */
    @Override
    protected void run( final Deque<Integer> aArgs )
    {
      final Integer percentage = aArgs.getLast();
      setProgressOnEDT( percentage.intValue() );
      updateActionsOnEDT();
    }
  }

  // CONSTANTS

  private static final Logger LOG = Logger.getLogger( ClientController.class.getName() );

  // VARIABLES

  private final BundleContext bundleContext;
  private final ActionManager actionManager;
  private final SignalDiagramController signalDiagramController;

  private final ConcurrentMap<String, Device> devices;
  private final ConcurrentMap<String, Tool<?>> tools;
  private final ConcurrentMap<String, Exporter> exporters;

  private final ProgressUpdatingRunnable progressAccumulatingRunnable;
  private final AccumulatingRepaintingRunnable repaintAccumulatingRunnable;

  private volatile ProjectManager projectManager;
  private volatile DataAcquisitionService dataAcquisitionService;
  private volatile MainFrame mainFrame;
  private volatile HostProperties hostProperties;
  private volatile UIColorSchemeManager colorSchemeManager;

  private volatile long acquisitionStartTime;

  // CONSTRUCTORS

  /**
   * Creates a new ClientController instance.
   *
   * @param aBundleContext
   *          the bundle context to use for interaction with the OSGi framework;
   * @param aHost
   *          the current host to use, cannot be <code>null</code>;
   * @param aProjectManager
   *          the project manager to use, cannot be <code>null</code>.
   */
  public ClientController( final BundleContext aBundleContext )
  {
    this.bundleContext = aBundleContext;

    this.devices = new ConcurrentHashMap<String, Device>();
    this.tools = new ConcurrentHashMap<String, Tool<?>>();
    this.exporters = new ConcurrentHashMap<String, Exporter>();

    this.actionManager = new ActionManager();

    this.signalDiagramController = new SignalDiagramController( this.actionManager );

    Runnable runner = new Runnable()
    {
      public void run()
      {
        ClientController.this.signalDiagramController.initialize();

        ActionManagerFactory.fillActionManager( ClientController.this.actionManager, ClientController.this );
      }
    };
    SwingComponentUtils.invokeOnEDT( runner );

    this.progressAccumulatingRunnable = new ProgressUpdatingRunnable();
    this.repaintAccumulatingRunnable = new AccumulatingRepaintingRunnable();
  }

  // METHODS

  /**
   * {@inheritDoc}
   */
  @Override
  public void acquisitionComplete( final AcquisitionResult aData )
  {
    try
    {
      getCurrentProject().setCapturedData( aData );
    }
    catch ( Exception exception )
    {
      exception.printStackTrace();
    }
    finally
    {
      updateActionsOnEDT();
    }
  }

  /**
   * {@inheritDoc}
   */
  @Override
  public void acquisitionEnded( final AcquisitionResultStatus aStatus )
  {
    if ( aStatus.isAborted() )
    {
      setStatusOnEDT( "Capture aborted! {0}", aStatus.getMessage() );
    }
    else if ( aStatus.isFailed() )
    {
      setStatusOnEDT( "Capture failed! {0}", aStatus.getMessage() );
    }
    else
    {
      long time = System.currentTimeMillis() - this.acquisitionStartTime;

      setStatusOnEDT( "Capture finished at {0,date,medium} {0,time,medium}, and took {1}.", new Date(),
          Unit.Time.format( time / 1.0e3 ) );
    }

    updateActionsOnEDT();
  }

  /**
   * {@inheritDoc}
   */
  @Override
  public void acquisitionInProgress( final int aPercentage )
  {
    this.progressAccumulatingRunnable.add( Integer.valueOf( aPercentage ) );
  }

  /**
   * {@inheritDoc}
   */
  @Override
  public void acquisitionStarted()
  {
    this.acquisitionStartTime = System.currentTimeMillis();

    updateActionsOnEDT();
  }

  /**
   * Adds a given device to this controller.
   * <p>
   * This method is called by the dependency manager.
   * </p>
   *
   * @param aDevice
   *          the device to add, cannot be <code>null</code>.
   */
  public void addDevice( final Device aDevice )
  {
    if ( this.devices.putIfAbsent( aDevice.getName(), aDevice ) == null )
    {
      this.actionManager.add( new SelectDeviceAction( this, aDevice.getName() ) );
    }
  }

  /**
   * Adds a given exporter to this controller.
   * <p>
   * This method is called by the dependency manager.
   * </p>
   *
   * @param aExporter
   *          the exporter to add, cannot be <code>null</code>.
   */
  public void addExporter( final Exporter aExporter )
  {
    if ( this.exporters.putIfAbsent( aExporter.getName(), aExporter ) == null )
    {
      this.actionManager.add( new ExportAction( this, aExporter.getName() ) );
    }
  }

  /**
   * Adds the given component provider to this controller, and does this
   * synchronously on the EDT.
   * <p>
   * This method is called by the Dependency Manager.
   * </p>
   *
   * @param aProvider
   *          the component provider, cannot be <code>null</code>.
   */
  public void addMenu( final ComponentProvider aProvider )
  {
    SwingComponentUtils.invokeOnEDT( new Runnable()
    {
      @Override
      public void run()
      {
        final JMenuBar menuBar = getMainMenuBar();

        if ( menuBar != null )
        {
          final JMenu menu = ( JMenu )aProvider.getComponent();
          menuBar.add( menu );

          aProvider.addedToContainer();

          menuBar.revalidate();
          menuBar.repaint();
        }
      }
    } );
  }

  /**
   * Adds a given tool to this controller.
   * <p>
   * This method is called by the dependency manager.
   * </p>
   *
   * @param aTool
   *          the tool to add, cannot be <code>null</code>.
   */
  public void addTool( final Tool<?> aTool )
  {
    if ( this.tools.putIfAbsent( aTool.getName(), aTool ) == null )
    {
      this.actionManager.add( new RunToolAction( this, aTool.getName(), aTool.getCategory() ) );
    }
  }

  /**
   * @see nl.lxtreme.ols.client.IClientController#cancelCapture()
   */
  public void cancelCapture()
  {
    final DataAcquisitionService acquisitionService = getDataAcquisitionService();
    final Device device = getDevice();
    if ( ( device == null ) || ( acquisitionService == null ) )
    {
      return;
    }

    try
    {
      acquisitionService.cancelAcquisition( device );
    }
    catch ( final IllegalStateException exception )
    {
      setStatusOnEDT( "No acquisition in progress!" );
    }
    catch ( final IOException exception )
    {
      setStatusOnEDT( "I/O problem: " + exception.getMessage() );

      // Make sure to handle IO-interrupted exceptions properly!
      if ( !HostUtils.handleInterruptedException( exception ) )
      {
        exception.printStackTrace();
      }
    }
    finally
    {
      updateActionsOnEDT();
    }
  }

  /**
   * {@inheritDoc}
   */
  public boolean captureData( final Window aParent )
  {
    final DataAcquisitionService acquisitionService = getDataAcquisitionService();
    final Device device = getDevice();
    if ( ( device == null ) || ( acquisitionService == null ) )
    {
      return false;
    }

    try
    {
      if ( device.setupCapture( aParent ) )
      {
        setStatusOnEDT( "Capture from {0} started at {1,date,medium} {1,time,medium} ...", device.getName(), new Date() );

        acquisitionService.acquireData( device );
        return true;
      }

      return false;
    }
    catch ( final IOException exception )
    {
      setStatusOnEDT( "I/O problem: " + exception.getMessage() );

      // Make sure to handle IO-interrupted exceptions properly!
      if ( !HostUtils.handleInterruptedException( exception ) )
      {
        exception.printStackTrace();
      }

      return false;
    }
    finally
    {
      updateActionsOnEDT();
    }
  }

  /**
   * {@inheritDoc}
   */
  @Override
  public void clearAnnotations()
  {
    for ( Channel channel : getCurrentDataSet().getChannels() )
    {
      channel.clearAnnotations();
    }

    repaintMainFrame();
  }

  /**
   * {@inheritDoc}
   */
  @Override
  public void clearAnnotations( final int aChannelIdx )
  {
    final Channel channel = getChannel( aChannelIdx );
    channel.clearAnnotations();

    repaintMainFrame();
  }

  /**
   * Creates a new project, causing all current data to be thrown away.
   */
  public void createNewProject()
  {
    this.projectManager.createNewProject();

    if ( this.mainFrame != null )
    {
      this.mainFrame.repaint();
    }

    updateActionsOnEDT();
  }

  /**
   * Terminates & shuts down the client.
   */
  public void exit()
  {
    // Close the main window ourselves; we should do this explicitly...
    if ( this.mainFrame != null )
    {
      this.mainFrame.setVisible( false );
      this.mainFrame.dispose();
      this.mainFrame = null;
    }

    try
    {
      // Stop the framework bundle; which should stop all other bundles as
      // well; the STOP_TRANSIENT option ensures the bundle is restarted the
      // next time...
      this.bundleContext.getBundle( 0 ).stop( Bundle.STOP_TRANSIENT );
    }
    catch ( final IllegalStateException ex )
    {
      LOG.warning( "Bundle context no longer valid while shutting down client?!" );

      // The bundle context is no longer valid; we're going to exit anyway, so
      // lets ignore this exception for now...
      System.exit( -1 );
    }
    catch ( final BundleException be )
    {
      LOG.warning( "Bundle context no longer valid while shutting down client?!" );

      System.exit( -1 );
    }
  }

  /**
   * Exports the current data set to a file using an {@link Exporter} with a
   * given name.
   *
   * @param aExporterName
   *          the name of the exporter to use, cannot be <code>null</code>;
   * @param aExportFile
   *          the file to export the results to, cannot be <code>null</code>.
   * @throws IOException
   *           in case of I/O problems during the export.
   */
  public void exportTo( final String aExporterName, final File aExportFile ) throws IOException
  {
    if ( this.mainFrame == null )
    {
      return;
    }

    OutputStream writer = null;

    try
    {
      writer = new FileOutputStream( aExportFile );

      final Exporter exporter = getExporter( aExporterName );
      exporter.export( getCurrentDataSet(), this.mainFrame.getDiagramScrollPane(), writer );

      setStatusOnEDT( "Export to {0} succesful ...", aExporterName );
    }
    finally
    {
      HostUtils.closeResource( writer );

      updateActionsOnEDT();
    }
  }

  /**
   * {@inheritDoc}
   */
  @Override
  public Action getAction( final String aID )
  {
    return this.actionManager.getAction( aID );
  }

  /**
   * Provides direct access to the cursor with the given index.
   *
   * @param aCursorIdx
   *          the index of the cursor, >= 0 && < {@link Ols#MAX_CURSORS}.
   * @return a cursor, never <code>null</code>.
   */
  public final Cursor getCursor( final int aCursorIdx )
  {
    final DataSet currentDataSet = getCurrentDataSet();
    if ( currentDataSet == null )
    {
      return null;
    }
    return currentDataSet.getCursor( aCursorIdx );
  }

  /**
   * Returns the current selected device.
   *
   * @return the selected device, can be <code>null</code> if no device is
   *         selected.
   */
  public final Device getDevice()
  {
    if ( this.mainFrame != null )
    {
      final String selectedDevice = this.mainFrame.getSelectedDeviceName();
      if ( selectedDevice != null )
      {
        return getDevice( selectedDevice );
      }
    }

    return null;
  }

  /**
   * Returns all available devices.
   *
   * @return an array of device names, never <code>null</code>, but an empty
   *         array is possible.
   */
  public String[] getDeviceNames()
  {
    List<String> result = new ArrayList<String>( this.devices.keySet() );
    // Make sure we've got a predictable order of names...
    Collections.sort( result );

    return result.toArray( new String[result.size()] );
  }

  /**
   * {@inheritDoc}
   */
  public Exporter getExporter( final String aName ) throws IllegalArgumentException
  {
    return this.exporters.get( aName );
  }

  /**
   * Returns all available exporters.
   *
   * @return an array of exporter names, never <code>null</code>, but an empty
   *         array is possible.
   */
  public String[] getExporterNames()
  {
    List<String> result = new ArrayList<String>( this.exporters.keySet() );
    // Make sure we've got a predictable order of names...
    Collections.sort( result );

    return result.toArray( new String[result.size()] );
  }

  /**
   * Returns the "supported" export extensions for the exporter with the given
   * name.
   *
   * @param aExporterName
   *          the name of the exporter to get the possible file extensions for,
   *          cannot be <code>null</code>.
   * @return an array of supported file extensions, never <code>null</code>.
   */
  public String[] getExportExtensions( final String aExporterName )
  {
    final Exporter exporter = getExporter( aExporterName );
    if ( exporter == null )
    {
      return new String[0];
    }
    return exporter.getFilenameExtentions();
  }

  /**
   * Returns the current host properties.
   *
   * @return the host properties, can be <code>null</code>.
   */
  public HostProperties getHostProperties()
  {
    return this.hostProperties;
  }

  /**
   * Returns the current project's file name.
   *
   * @return the project file name, can be <code>null</code> if it is never
   *         saved before.
   */
  public File getProjectFilename()
  {
    return getCurrentProject().getFilename();
  }

  /**
   * Returns the signal diagram controller.
   *
   * @return a signal diagram controller, never <code>null</code>.
   */
  public SignalDiagramController getSignalDiagramController()
  {
    return this.signalDiagramController;
  }

  /**
   * Returns all available tools.
   *
   * @return an array of tool names, never <code>null</code>, but an empty array
   *         is possible.
   */
  public String[] getToolNames()
  {
    List<String> result = new ArrayList<String>( this.tools.keySet() );
    // Make sure we've got a predictable order of names...
    Collections.sort( result );

    return result.toArray( new String[result.size()] );
  }

  /**
   * {@inheritDoc}
   */
  public void gotoTriggerPosition()
  {
    final AcquisitionResult capturedData = getCurrentDataSet().getCapturedData();
    if ( ( capturedData != null ) && capturedData.hasTriggerData() )
    {
      final long position = capturedData.getTriggerPosition();
      this.mainFrame.gotoPosition( 0, position );
    }
  }

  /**
   * {@inheritDoc}
   */
  @Override
  public final boolean handleAbout()
  {
    showAboutBox();
    return true;
  }

  /**
   * {@inheritDoc}
   */
  @Override
  public final boolean handlePreferences()
  {
    showPreferencesDialog( getMainFrame() );
    return true;
  }

  /**
   * {@inheritDoc}
   */
  @Override
  public final boolean handleQuit()
  {
    exit();
    // On Mac OS, it appears that if we acknowledge this event, the system
    // shuts down our application for us, thereby not calling our stop/shutdown
    // hooks... By returning false, we're not acknowledging the quit action to
    // the system, but instead do it all on our own...
    return false;
  }

  /**
   * Returns whether or not there's captured data to display.
   *
   * @return <code>true</code> if there's captured data, <code>false</code>
   *         otherwise.
   */
  public boolean hasCapturedData()
  {
    final DataSet currentDataSet = getCurrentDataSet();
    return ( currentDataSet != null ) && ( currentDataSet.getCapturedData() != null );
  }

  /**
   * {@inheritDoc}
   */
  @Override
  public final boolean hasPreferences()
  {
    return true;
  }

  /**
   * Runs the tool denoted by the given name.
   *
   * @param aToolName
   *          the name of the tool to run, cannot be <code>null</code>;
   * @param aParent
   *          the parent window to use, can be <code>null</code>.
   */
  public void invokeTool( final String aToolName, final Window aParent )
  {
    if ( LOG.isLoggable( Level.INFO ) )
    {
      LOG.log( Level.INFO, "Running tool: \"{0}\" ...", aToolName );
    }

    final Tool<?> tool = getTool( aToolName );
    if ( tool == null )
    {
      JOptionPane.showMessageDialog( aParent, "No such tool found: " + aToolName, "Error ...",
          JOptionPane.ERROR_MESSAGE );
    }
    else
    {
      final ToolContext context = createToolContext();
      tool.invoke( aParent, context );
    }

    updateActionsOnEDT();
  }

  /**
   * Returns whether or not a device is selected.
   *
   * @return <code>true</code> if a device is selected, <code>false</code>
   *         otherwise.
   */
  public boolean isDeviceSelected()
  {
    return ( this.mainFrame != null ) && ( this.mainFrame.getSelectedDeviceName() != null );
  }

  /**
   * {@inheritDoc}
   */
  public boolean isDeviceSetup()
  {
    return isDeviceSelected() && getDevice().isSetup();
  }

  /**
   * Returns whether or not the current project is changed.
   *
   * @return <code>true</code> if the current project is changed,
   *         <code>false</code> otherwise.
   */
  public boolean isProjectChanged()
  {
    final Project currentProject = getCurrentProject();
    if ( currentProject == null )
    {
      return false;
    }
    return currentProject.isChanged();
  }

  /**
   * {@inheritDoc}
   */
  @Override
  public void onAnnotation( final Annotation<?> aAnnotation )
  {
    final Channel channel = getChannel( aAnnotation.getChannel() );
    channel.addAnnotation( aAnnotation );

    // Accumulate repaint events to avoid an avalanche of events on the EDT...
    this.repaintAccumulatingRunnable.add( ( Void )null );
  }

  /**
   * Opens a given file as OLS-data file.
   *
   * @param aFile
   *          the file to open, cannot be <code>null</code>.
   * @throws IOException
   *           in case opening/reading from the given file failed.
   */
  public void openDataFile( final File aFile ) throws IOException
  {
    final FileReader reader = new FileReader( aFile );

    try
    {
      getCurrentProject().readData( reader );

      setStatusOnEDT( "Capture data loaded from {0} ...", aFile.getName() );
    }
    finally
    {
      HostUtils.closeResource( reader );
      updateActionsOnEDT();
    }
  }

  /**
   * Opens a given file as OLS-project file.
   *
   * @param aFile
   *          the file to open, cannot be <code>null</code>.
   * @throws IOException
   *           in case opening/reading from the given file failed.
   */
  public void openProjectFile( final File aFile ) throws IOException
  {
    FileInputStream fis = null;

    try
    {
      fis = new FileInputStream( aFile );

      this.projectManager.loadProject( fis );

      final Project project = getCurrentProject();
      project.setFilename( aFile );

      setStatusOnEDT( "Project {0} loaded ...", project.getName() );
    }
    finally
    {
      HostUtils.closeResource( fis );
      updateActionsOnEDT();
    }
  }

  /**
   * Removes a given device from this controller.
   * <p>
   * This method is called by the dependency manager.
   * </p>
   *
   * @param aDevice
   *          the device to remove, cannot be <code>null</code>.
   */
  public void removeDevice( final Device aDevice )
  {
    synchronized ( this.devices )
    {
      final String deviceName = aDevice.getName();

      this.devices.remove( deviceName );

      try
      {
        IManagedAction action = this.actionManager.getAction( SelectDeviceAction.getID( deviceName ) );
        this.actionManager.remove( action );
      }
      catch ( IllegalArgumentException exception )
      {
        LOG.log( Level.FINE, "No action for device {}?!", deviceName );
      }
    }

    updateActionsOnEDT();
  }

  /**
   * Removes a given exporter from this controller.
   * <p>
   * This method is called by the dependency manager.
   * </p>
   *
   * @param aExporter
   *          the exporter to remove, cannot be <code>null</code>.
   */
  public void removeExporter( final Exporter aExporter )
  {
    synchronized ( this.exporters )
    {
      final String exporterName = aExporter.getName();

      this.exporters.remove( exporterName );

      try
      {
        IManagedAction action = this.actionManager.getAction( ExportAction.getID( exporterName ) );
        this.actionManager.remove( action );
      }
      catch ( IllegalArgumentException exception )
      {
        LOG.log( Level.FINE, "No action for exporter {}?!", exporterName );
      }
    }
  }

  /**
   * Removes the given component provider from this controller, and does this
   * synchronously on the EDT.
   * <p>
   * This method is called by the Dependency Manager.
   * </p>
   *
   * @param aProvider
   *          the menu component provider, cannot be <code>null</code>.
   */
  public void removeMenu( final ComponentProvider aProvider )
  {
    SwingComponentUtils.invokeOnEDT( new Runnable()
    {
      @Override
      public void run()
      {
        final JMenuBar menuBar = getMainMenuBar();
        if ( menuBar != null )
        {
          aProvider.removedFromContainer();

          menuBar.remove( aProvider.getComponent() );

          menuBar.revalidate();
          menuBar.repaint();
        }
      }
    } );
  }

  /**
   * Removes a given tool from this controller.
   * <p>
   * This method is called by the dependency manager.
   * </p>
   *
   * @param aTool
   *          the tool to remove, cannot be <code>null</code>.
   */
  public void removeTool( final Tool<?> aTool )
  {
    synchronized ( this.tools )
    {
      final String toolName = aTool.getName();

      this.tools.remove( toolName );

      try
      {
        IManagedAction action = this.actionManager.getAction( RunToolAction.getID( toolName ) );
        this.actionManager.remove( action );
      }
      catch ( IllegalArgumentException exception )
      {
        LOG.log( Level.FINE, "No action for tool {}?!", toolName );
      }
    }
  }

  /**
   * Restarts a new acquisition with the current device and with its current
   * settings.
   */
  public void repeatCaptureData()
  {
    final DataAcquisitionService acquisitionService = getDataAcquisitionService();
    final Device devCtrl = getDevice();

    if ( ( devCtrl == null ) || ( acquisitionService == null ) )
    {
      return;
    }

    try
    {
      setStatusOnEDT( "Capture from {0} started at {1,date,medium} {1,time,medium} ...", devCtrl.getName(), new Date() );

      acquisitionService.acquireData( devCtrl );
    }
    catch ( final IOException exception )
    {
      setStatusOnEDT( "I/O problem: " + exception.getMessage() );

      exception.printStackTrace();

      // Make sure to handle IO-interrupted exceptions properly!
      HostUtils.handleInterruptedException( exception );
    }
    finally
    {
      updateActionsOnEDT();
    }
  }

  /**
   * Stores the current acquisition data to the given file, in the OLS-data file
   * format.
   *
   * @param aFile
   *          the file to write the data to, cannot be <code>null</code>.
   * @throws IOException
   *           in case of errors during opening/writing to the file.
   */
  public void saveDataFile( final File aFile ) throws IOException
  {
    final FileWriter writer = new FileWriter( aFile );
    try
    {
      getCurrentProject().writeData( writer );

      setStatusOnEDT( "Capture data saved to {0} ...", aFile.getName() );
    }
    finally
    {
      HostUtils.closeResource( writer );
    }
  }

  /**
   * Stores the current acquisition data to the given file, in the OLS-project
   * file format.
   *
   * @param aName
   *          the name of the project to store, cannot be <code>null</code>;
   * @param aFile
   *          the file to write the data to, cannot be <code>null</code>.
   * @throws IOException
   *           in case of errors during opening/writing to the file.
   */
  public void saveProjectFile( final String aName, final File aFile ) throws IOException
  {
    FileOutputStream out = null;
    try
    {
      final Project project = getCurrentProject();
      project.setFilename( aFile );
      project.setName( aName );

      out = new FileOutputStream( aFile );
      this.projectManager.saveProject( out );

      setStatusOnEDT( "Project {0} saved ...", aName );
    }
    finally
    {
      HostUtils.closeResource( out );
    }
  }

  /**
   * Selects the device with the given name.
   *
   * @param aDeviceName
   *          the name of the device to select, can be <code>null</code> to
   *          deselect the current selected device.
   */
  public void selectDevice( final String aDeviceName )
  {
    if ( this.mainFrame != null )
    {
      this.mainFrame.setSelectedDeviceName( aDeviceName );
    }
    // Make sure the action reflect the current situation...
    updateActionsOnEDT();
  }

  /**
   * Shows the "about OLS" dialog on screen. the parent window to use, can be
   * <code>null</code>.
   */
  public void showAboutBox()
  {
    if ( this.mainFrame != null )
    {
      this.mainFrame.showAboutBox();
    }
  }

  /**
   * Shows a dialog with all current bundles.
   *
   * @param aOwner
   *          the owning window to use, can be <code>null</code>.
   */
  public void showBundlesDialog( final Window aOwner )
  {
    final BundlesDialog dialog = new BundlesDialog( aOwner, this.bundleContext );
    if ( dialog.showDialog() )
    {
      dialog.dispose();
    }
  }

  /**
   * Shows the global preferences dialog.
   *
   * @param aParent
   *          the parent window of the dialog, can be <code>null</code>.
   */
  public void showPreferencesDialog( final Window aParent )
  {
    final PreferencesDialog dialog = new PreferencesDialog( aParent, this.colorSchemeManager );

    final DependencyManager dm = new DependencyManager( this.bundleContext );
    final Component comp = dm.createComponent();

    comp.setImplementation( dialog ).add( dm.createServiceDependency() //
        .setService( ConfigurationAdmin.class ) //
        .setInstanceBound( true ) //
        .setRequired( true ) ) //
        .addStateListener( new ComponentStateAdapter()
        {
          @Override
          public void started( final Component aComponent )
          {
            if ( dialog.showDialog() )
            {
              // Update the default settings (if needed)...
              updateDefaultSettings();

              // Ensure all UI-related changes are immediately visible...
              repaintMainFrame();
            }

            // All changes are persisted automatically...
            dm.remove( comp );
          }
        } );

    dm.add( comp );
  }

  /**
   * Called by the dependency manager when this component is about to be
   * started.
   */
  public final void start()
  {
    final HostProperties hostProperties = getHostProperties();

    initOSSpecifics( hostProperties.getShortName(), hostProperties.getVersion() );

    // Make sure we're running on the EDT to ensure the Swing threading model is
    // correctly defined...
    SwingUtilities.invokeLater( new Runnable()
    {
      @Override
      public void run()
      {
        // Cause exceptions to be shown in a more user-friendly way...
        JErrorDialog.installSwingExceptionHandler();

        final File dataStorage = ClientController.this.bundleContext.getDataFile( "" );

        final MainFrame mf = new MainFrame( new DockController( dataStorage, getSignalDiagramController() ),
            ClientController.this );
        setMainFrame( mf );

        // ensure that all changes to cursors are reflected in the UI...
        ClientController.this.signalDiagramController.addCursorChangeListener( new CursorActionListener() );
        updateDefaultSettings();

        mf.setTitle( hostProperties.getFullName() );
        mf.setStatus( "{0} v{1} ready ...", hostProperties.getShortName(), hostProperties.getVersion() );
        mf.setVisible( true );

        LOG.info( "Client started ..." );
      }
    } );
  }

  /**
   * Called by the dependency manager when this component is about to be
   * stopped.
   */
  public final void stop()
  {
    this.devices.clear();
    this.tools.clear();
    this.exporters.clear();

    // Make sure we're running on the EDT to ensure the Swing threading model is
    // correctly defined...
    SwingUtilities.invokeLater( new Runnable()
    {
      @Override
      public void run()
      {
        final MainFrame mf = getMainFrame();
        if ( mf != null )
        {
          // Safety guard: also loop through all unclosed frames and close them
          // as well...
          final Window[] openWindows = Window.getWindows();
          for ( Window window : openWindows )
          {
            LOG.log( Level.FINE, "(Forced) closing window {0} ...", window );

            window.setVisible( false );
            window.dispose();
          }
          // release all resources...
          mf.close();
          // Make sure the event listeners are deregistered...
          removeMainFrame( mf );
        }

        JErrorDialog.uninstallSwingExceptionHandler();

        LOG.info( "Client stopped ..." );
      }
    } );
  }

  /**
   * Returns the current data set.
   *
   * @return the current data set, never <code>null</code>.
   */
  final DataSet getCurrentDataSet()
  {
    final Project currentProject = getCurrentProject();
    if ( currentProject == null )
    {
      return null;
    }
    return currentProject.getDataSet();
  }

  /**
   * Returns the current project.
   *
   * @return the current project, never <code>null</code>.
   */
  final Project getCurrentProject()
  {
    if ( this.projectManager == null )
    {
      return null;
    }
    return this.projectManager.getCurrentProject();
  }

  /**
   * Returns the current main frame.
   *
   * @return the main frame, can be <code>null</code>.
   */
  final MainFrame getMainFrame()
  {
    return this.mainFrame;
  }

  /**
   * Returns the main menu bar.
   *
   * @return the main menu bar, can be <code>null</code>.
   */
  final JMenuBar getMainMenuBar()
  {
    JMenuBar result = null;
    if ( this.mainFrame != null )
    {
      result = this.mainFrame.getJMenuBar();
    }
    return result;
  }

  /**
   * @param aMainFrame
   *          the main frame to remove, cannot be <code>null</code>.
   */
  final void removeMainFrame( final MainFrame aMainFrame )
  {
    if ( this.projectManager != null )
    {
      this.projectManager.removePropertyChangeListener( aMainFrame );
    }

    this.mainFrame = null;
  }

  /**
   * Called by the dependency manager when the project manager service is going
   * away.
   *
   * @param aProjectManager
   *          the old project manager to remove.
   */
  final void removeProjectManager( final ProjectManager aProjectManager )
  {
    if ( this.signalDiagramController != null )
    {
      aProjectManager.removePropertyChangeListener( this.signalDiagramController );
    }
    if ( this.mainFrame != null )
    {
      aProjectManager.removePropertyChangeListener( this.mainFrame );
    }

    this.projectManager = null;
  }

  /**
   * @param aMainFrame
   *          the main frame to set, cannot be <code>null</code>.
   */
  final void setMainFrame( final MainFrame aMainFrame )
  {
    this.mainFrame = aMainFrame;

    if ( this.projectManager != null )
    {
      this.projectManager.addPropertyChangeListener( this.mainFrame );
    }
  }

  /**
   * Updates the progress on the EventDispatchThread (EDT).
   */
  final void setProgressOnEDT( final int aPercentage )
  {
    if ( this.mainFrame != null )
    {
      SwingComponentUtils.invokeOnEDT( new Runnable()
      {
        @Override
        public void run()
        {
          ClientController.this.mainFrame.setProgress( aPercentage );
        }
      } );
    }
  }

  /**
   * Called by the dependency manager when a (new) project manager service
   * becomes available.
   *
   * @param aProjectManager
   *          the projectManager to set.
   */
  final void setProjectManager( final ProjectManager aProjectManager )
  {
    this.projectManager = aProjectManager;

    if ( this.projectManager != null )
    {
      if ( this.signalDiagramController != null )
      {
        this.projectManager.addPropertyChangeListener( this.signalDiagramController );
      }
      if ( this.mainFrame != null )
      {
        this.projectManager.addPropertyChangeListener( this.mainFrame );
      }
    }
  }

  /**
   * Sets the given message + arguments as status message.
   *
   * @param aMessage
   *          the message to set;
   * @param aMessageArgs
   *          the (optional) arguments of the message.
   */
  void setStatusOnEDT( final String aMessage, final Object... aMessageArgs )
  {
    if ( this.mainFrame != null )
    {
      SwingComponentUtils.invokeOnEDT( new Runnable()
      {
        @Override
        public void run()
        {
          ClientController.this.mainFrame.setStatus( aMessage, aMessageArgs );
        }
      } );
    }
  }

  /**
   * Updates the actions on the EventDispatchThread (EDT).
   */
  final void updateActionsOnEDT()
  {
    SwingComponentUtils.invokeOnEDT( new Runnable()
    {
      @Override
      public void run()
      {
        final DataAcquisitionService acquisitionService = getDataAcquisitionService();
        final Device device = getDevice();

        final boolean deviceControllerSet = ( device != null );
        final boolean deviceCapturing = ( acquisitionService != null ) && acquisitionService.isAcquiring();
        final boolean deviceSetup = deviceControllerSet && !deviceCapturing && device.isSetup();

        getAction( CaptureAction.ID ).setEnabled( deviceControllerSet );
        getAction( CancelCaptureAction.ID ).setEnabled( deviceCapturing );
        getAction( RepeatCaptureAction.ID ).setEnabled( deviceSetup );

        final boolean projectChanged = isProjectChanged();
        final boolean projectSavedBefore = !isAnonymousProject();
        final boolean dataAvailable = hasCapturedData();
        final boolean hasTriggerData = hasTriggerData();
        final boolean cursorsEnabled = areCursorsEnabled();
        final boolean enableCursors = dataAvailable && cursorsEnabled;

        getAction( SaveProjectAction.ID ).setEnabled( projectChanged );
        getAction( SaveProjectAsAction.ID ).setEnabled( projectSavedBefore && projectChanged );
        getAction( SaveDataFileAction.ID ).setEnabled( dataAvailable );

        getAction( GotoTriggerAction.ID ).setEnabled( dataAvailable && hasTriggerData );

        // Update the cursor actions accordingly...
        getAction( SetCursorModeAction.ID ).setEnabled( dataAvailable );
        getAction( SetCursorModeAction.ID ).putValue( Action.SELECTED_KEY, Boolean.valueOf( cursorsEnabled ) );

        getAction( SmartJumpAction.getJumpLeftID() ).setEnabled( dataAvailable );
        getAction( SmartJumpAction.getJumpRightID() ).setEnabled( dataAvailable );

        boolean anyCursorSet = false;
        for ( int c = 0; c < Ols.MAX_CURSORS; c++ )
        {
          final boolean cursorPositionSet = isCursorSet( c );
          anyCursorSet |= cursorPositionSet;

          final boolean gotoCursorNEnabled = enableCursors && cursorPositionSet;
          getAction( GotoNthCursorAction.getID( c ) ).setEnabled( gotoCursorNEnabled );
        }

        final boolean snapCursorMode = getSignalDiagramController().getSignalDiagramModel().isSnapCursorMode();
        getAction( SetCursorSnapModeAction.ID ).putValue( Action.SELECTED_KEY, Boolean.valueOf( snapCursorMode ) );

        getAction( GotoFirstCursorAction.ID ).setEnabled( enableCursors && anyCursorSet );
        getAction( GotoLastCursorAction.ID ).setEnabled( enableCursors && anyCursorSet );

        getAction( DeleteAllCursorsAction.ID ).setEnabled( enableCursors && anyCursorSet );
        getAction( RemoveAnnotationsAction.ID ).setEnabled( dataAvailable );

        getAction( SetMeasurementModeAction.ID ).setEnabled( dataAvailable );
        getAction( ShowManagerViewAction.ID ).setEnabled( dataAvailable );

        // Update the tools...
        final IManagedAction[] toolActions = getActionsByType( RunToolAction.class );
        for ( final IManagedAction toolAction : toolActions )
        {
          if ( !ToolCategory.OTHER.equals( ( ( RunToolAction )toolAction ).getCategory() ) )
          {
            toolAction.setEnabled( dataAvailable );
          }
        }

        // Update the exporters...
        final IManagedAction[] exportActions = getActionsByType( ExportAction.class );
        for ( final IManagedAction exportAction : exportActions )
        {
          exportAction.setEnabled( dataAvailable );
        }
      }
    } );
  }

  /**
   * Updates the default settings.
   */
  final void updateDefaultSettings()
  {
    this.signalDiagramController.setDefaultSettings();
  }

  /**
   * Returns whether or not the cursors are enabled.
   *
   * @return <code>true</code> if cursors are enabled, <code>false</code>
   *         otherwise.
   */
  protected boolean areCursorsEnabled()
  {
    return this.signalDiagramController.getSignalDiagramModel().isCursorMode();
  }

  /**
   * Returns all actions of a given type.
   *
   * @param aActionType
   *          the type of action to return, cannot be <code>null</code>.
   * @return an array of requested actions, never <code>null</code>.
   */
  protected IManagedAction[] getActionsByType( final Class<? extends IManagedAction> aActionType )
  {
    if ( this.actionManager == null )
    {
      return new IManagedAction[0];
    }
    return this.actionManager.getActionByType( aActionType );
  }

  /**
   * Returns whether or not there is trigger data available.
   *
   * @return <code>true</code> if there is trigger data available,
   *         <code>false</code> otherwise.
   */
  protected boolean hasTriggerData()
  {
    final DataSet currentDataSet = getCurrentDataSet();
    if ( ( currentDataSet == null ) || ( currentDataSet.getCapturedData() == null ) )
    {
      return false;
    }
    return currentDataSet.getCapturedData().hasTriggerData();
  }

  /**
   * Returns whether or not the current project is "anonymous", i.e., not yet
   * saved.
   *
   * @return <code>true</code> if the current project is anonymous,
   *         <code>false</code> otherwise.
   */
  protected boolean isAnonymousProject()
  {
    final Project currentProject = getCurrentProject();
    if ( currentProject == null )
    {
      return false;
    }
    return currentProject.getFilename() == null;
  }

  /**
   * Returns whether or not a cursor with the given index is set.
   *
   * @param aCursorIdx
   *          the index of the cursor test.
   * @return <code>true</code> if the cursor with the given index is set,
   *         <code>false</code> otherwise.
   */
  protected boolean isCursorSet( final int aCursorIdx )
  {
    final Cursor cursor = getCursor( aCursorIdx );
    if ( cursor == null )
    {
      return false;
    }
    return cursor.isDefined();
  }

  /**
   * Creates the tool context denoting the range of samples that should be
   * analysed by a tool.
   *
   * @return a tool context, never <code>null</code>.
   */
  private ToolContext createToolContext()
  {
    int startOfDecode = -1;
    int endOfDecode = -1;

    final DataSet dataSet = getCurrentDataSet();
    final AcquisitionResult capturedData = dataSet.getCapturedData();

    if ( capturedData != null )
    {
      final int dataLength = capturedData.getValues().length;
      if ( areCursorsEnabled() )
      {
        if ( isCursorSet( 0 ) )
        {
          final Cursor cursor1 = dataSet.getCursor( 0 );
          startOfDecode = capturedData.getSampleIndex( cursor1.getTimestamp() ) - 1;
        }
        if ( isCursorSet( 1 ) )
        {
          final Cursor cursor2 = dataSet.getCursor( 1 );
          endOfDecode = capturedData.getSampleIndex( cursor2.getTimestamp() ) + 1;
        }
      }
      else
      {
        startOfDecode = 0;
        endOfDecode = dataLength;
      }

      startOfDecode = Math.max( 0, startOfDecode );
      if ( ( endOfDecode < 0 ) || ( endOfDecode >= dataLength ) )
      {
        endOfDecode = dataLength - 1;
      }
    }

    return new DefaultToolContext( startOfDecode, endOfDecode, dataSet );
  }

  /**
   * Returns the {@link Channel} with the given index.
   *
   * @param aChannelIdx
   *          the index of the channel to return, >= 0.
   * @return a {@link Channel} instance, never <code>null</code>.
   */
  private Channel getChannel( final int aChannelIdx )
  {
    final DataSet currentDataSet = getCurrentDataSet();
    if ( currentDataSet == null )
    {
      return null;
    }
    return currentDataSet.getChannel( aChannelIdx );
  }

  /**
   * Returns the data acquisition service.
   *
   * @return a data acquisition service, never <code>null</code>.
   */
  private DataAcquisitionService getDataAcquisitionService()
  {
    return this.dataAcquisitionService;
  }

  /**
   * {@inheritDoc}
   */
  private Device getDevice( final String aName ) throws IllegalArgumentException
  {
    return this.devices.get( aName );
  }

  /**
   * {@inheritDoc}
   */
  private Tool<?> getTool( final String aName ) throws IllegalArgumentException
  {
    return this.tools.get( aName );
  }

  /**
   * Initializes the OS-specific stuff.
   *
   * @param aApplicationName
   *          the name of the application (when this needs to be passed to the
   *          guest OS);
   * @param aVersion
   *          the version of the application.
   * @param aApplicationCallback
   *          the application callback used to report application events on some
   *          platforms (Mac OS), may be <code>null</code>.
   */
  private void initOSSpecifics( final String aApplicationName, String aVersion )
  {
    System.setProperty( "nl.lxtreme.ols.client.version", aVersion );

    // Use the defined email address...
    System.setProperty( JErrorDialog.PROPERTY_REPORT_INCIDENT_EMAIL_ADDRESS,
        this.hostProperties.getReportIncidentAddress() );

    if ( this.hostProperties.isDebugMode() )
    {
      // Install a custom repaint manager that detects whether Swing
      // components are created outside the EDT; if so, it will yield a
      // stack trace to the offending parts of the code...
      ThreadViolationDetectionRepaintManager.install();
    }

    final HostInfo hostInfo = HostUtils.getHostInfo();
    if ( hostInfo.isMacOS() )
    {
      // Moves the main menu bar to the screen menu bar location...
      System.setProperty( "apple.laf.useScreenMenuBar", "true" );
      System.setProperty( "apple.awt.graphics.EnableQ2DX", "true" );
      System.setProperty( "com.apple.mrj.application.apple.menu.about.name", aApplicationName );
      System.setProperty( "com.apple.mrj.application.growbox.intrudes", "false" );
      System.setProperty( "com.apple.mrj.application.live-resize", "false" );
      System.setProperty( "com.apple.macos.smallTabs", "true" );
      System.setProperty( "apple.eawt.quitStrategy", "CLOSE_ALL_WINDOWS" );

      // Install an additional accelerator (Cmd+W) for closing option panes...
      ActionMap map = ( ActionMap )UIManager.get( "OptionPane.actionMap" );
      if ( map == null )
      {
        map = new ActionMap();
        UIManager.put( "OptionPane.actionMap", map );
      }
      map.put( "close", new CloseOptionPaneAction() );

      UIManager.put( "OptionPane.windowBindings", //
          new Object[] { SwingComponentUtils.createMenuKeyMask( KeyEvent.VK_W ), "close", "ESCAPE", "close" } );
    }
    else if ( hostInfo.isUnix() )
    {
      UIManager.put( "Application.useSystemFontSettings", Boolean.FALSE );
      setLookAndFeel( "com.jgoodies.looks.plastic.Plastic3DLookAndFeel" );
    }
    else if ( hostInfo.isWindows() )
    {
      UIManager.put( "Application.useSystemFontSettings", Boolean.TRUE );
      setLookAndFeel( "com.jgoodies.looks.plastic.PlasticXPLookAndFeel" );
    }
  }

  /**
   * Dispatches a request to repaint the entire main frame.
   */
  private void repaintMainFrame()
  {
    if ( this.mainFrame != null )
    {
      SwingComponentUtils.invokeOnEDT( new Runnable()
      {
        @Override
        public void run()
        {
          ClientController.this.mainFrame.repaint();
        }
      } );
    }
  }

  /**
   * @param aLookAndFeelClass
   */
  private void setLookAndFeel( final String aLookAndFeelClassName )
  {
    final UIDefaults defaults = UIManager.getLookAndFeelDefaults();
    // to make sure we always use system class loader
    defaults.put( "ClassLoader", Activator.class.getClassLoader() );

    try
    {
      UIManager.setLookAndFeel( aLookAndFeelClassName );
    }
    catch ( Exception exception )
    {
      // Make sure to handle IO-interrupted exceptions properly!
      if ( !HostUtils.handleInterruptedException( exception ) )
      {
        System.err.println( "Failed to set look and feel to: " + aLookAndFeelClassName );
        setLookAndFeel( UIManager.getSystemLookAndFeelClassName() );
      }
    }
  }
}
TOP

Related Classes of nl.lxtreme.ols.client.ClientController

TOP
Copyright © 2018 www.massapi.com. All rights reserved.
All source code are property of their respective owners. Java is a trademark of Sun Microsystems, Inc and owned by ORACLE Inc. Contact coftware#gmail.com.