Jump to: navigation, search

Scout/HowTo/3.8/Uncached images and icons

< Scout‎ | HowTo‎ | 3.8


Scout
Wiki Home
Website
DownloadGit
Community
ForumsBlogTwitter
Bugzilla
Bugzilla


This how-to describes how to work with uncached images and icons.

Background

When using icons/images in a table or a form an IconLocator (which in turn uses a [IconProviderService]) is used. The default IconLocator used for the Swing environment is org.eclipse.scout.rt.ui.swing.SwingIconLocator, the one for the SWT environment is SWTIconLocator. The SwingIconLocator caches the images when they are first requested. This means that whenever a setIconId() method on a form field or table column is called with an iconId that has previously been used, the first image retrieved under that name will be returned, even if the image has changed since then (because it has changed on disk, for example).

There are various options to work around this but while all of them will work for form fields (AbstractImageField), not all of them work for table columns. This page tries to list the various methods that can be used together with their advantages and disadvantages.

Solutions

Custom field: UncachedImageField

Synopsis: Create a "deep" copy of the ImageField model and renderer and modify it to use an uncached IconLocator
Advantages:
  • Each image field on a form can either be of type AbstractImageField (cached) or AbstractUncachedImageField, allowing for fine granularity which images are cached or not
Disadvantages:
  • The ImageField classes must be copied fully. Apart from code bloat, this also introduces redundancy and the need to mirror any updates to the classes that were copied to the copies
  • Works for forms but not tables; theoretically, the same method could be applied to the TableField classes, but there the code bloat would be even greater
Status: Not recommended
Detailed steps needed:
  • Create the class org.eclipse.minicrm.ui.swing.SwingUncachedIconLocator:
public class SwingUncachedIconLocator extends SwingIconLocator {
  private static final IScoutLogger LOG = ScoutLogManager.getLogger(SwingUncachedIconLocator.class);

  public static final Pattern IMAGE_WITH_STATE_PATTERN = Pattern.compile("(.*)(_active|_disabled|_mouse|_mouse_over|_open|_over|_pressed|_rollover|_selected)", Pattern.CASE_INSENSITIVE);

  private final Object m_cacheLock = new Object();

  private IIconLocator m_iconLocator;

  public SwingUncachedIconLocator(IIconLocator iconLocator) {
    super(iconLocator);
    m_iconLocator = iconLocator;
  }

  @Override
  public Image getImage(String name) {
    if (name == null || AbstractIcons.Null.equals(name) || name.length() == 0) {
      return null;
    }
    Image img;
    synchronized (m_cacheLock) {
      img = createImageImpl(name);
      if (img == null) {
        img = autoCreateMissingImage(name);
      }
      if (LOG.isDebugEnabled()) {
        LOG.debug("load image '" + name + "' as " + img);
      }
      if (img == null) {
        warnImageNotFound(name);
      }
    }
    return img;
  }

  private Image createImageImpl(String name) {
    IconSpec iconSpec = m_iconLocator.getIconSpec(name);
    if (iconSpec != null) {
      Image img = Toolkit.getDefaultToolkit().createImage(iconSpec.getContent());
      if (img != null) {
        //decorate window icon in development mode
        if (Platform.inDevelopmentMode() && name != null && name.matches("^(window\\d+|tray)$")) {
          img = decorateForDevelopment(img);
        }
      }
      return img;
    }
    return null;
  }
}
  • Open org.eclipse.scout.rt.client.ui.form.fields.imagebox.IImageField and save it as org.eclipse.minicrm.client.form.fields.ext.IUncachedImageField and replace all occurences of "ImageField" with "UncachedImageField"
  • Open org.eclipse.scout.rt.client.ui.form.fields.imagebox.IImageFieldUIFacade and save it as org.eclipse.minicrm.client.form.fields.ext.IUncachedImageFieldUIFacade and replace all occurences of "ImageField" with "UncachedImageField"
  • Open org.eclipse.scout.rt.client.ui.form.fields.imagebox.ImageFieldEvent and save it as org.eclipse.minicrm.client.form.fields.ext.UncachedImageFieldEvent and replace all occurences of "ImageField" with "UncachedImageField"
  • Open org.eclipse.scout.rt.client.ui.form.fields.imagebox.ImageFieldListener and save it as org.eclipse.minicrm.client.form.fields.ext.UncachedImageFieldListener and replace all occurences of "ImageField" with "UncachedImageField"
  • Open org.eclipse.scout.rt.client.ui.form.fields.imagebox.AbstractImageField and save it as org.eclipse.minicrm.client.form.fields.ext.AbstractUncachedImageField and replace all occurences of "ImageField" with "UncachedImageField"
  • Open org.eclipse.scout.rt.ui.swing.form.fields.imagebox.ISwingScoutImageField and save it as org.eclipse.minicrm.ui.swing.form.fields.ext.ISwingScoutUncachedImageField and replace all occurences of "ImageField" with "UncachedImageField"
  • Open org.eclipse.scout.rt.ui.swing.form.fields.imagebox.SwingScoutImageField and save it as org.eclipse.minicrm.ui.swing.form.fields.ext.SwingScoutUncachedImageField and replace all occurences of "ImageField" with "UncachedImageField",
    then add the following code:
  private SwingUncachedIconLocator m_uncachedIconLocator;

  protected SwingUncachedIconLocator getUncachedIconLocator() {
    if (m_uncachedIconLocator == null) {
      m_uncachedIconLocator = new SwingUncachedIconLocator(getSwingEnvironment().getScoutSession().getIconLocator());
    }
    return m_uncachedIconLocator;
  }

and modify the setImageFromScout() method as follows:

  protected void setImageFromScout(String imageId, Object image) {
    if (image == null) {
      if (imageId != null) {
        // try to use uncached image
        image = getUncachedIconLocator().getImage(imageId);
        if (image == null) {
          // as fallback, use cached image
          image = getSwingEnvironment().getImage(imageId);
        }
      }
    }
    getSwingImageViewer().setImage(image);
  }



.

Extending SwingScoutImageField

Synopsis: Provide a new plugin in which UncachedSwingScoutImageField extends SwingScoutImageField and uses the UncachedIconLocator.
Advantages:
  • No code redundancies
Disadvantages:
  • All AbstractImageFields become uncached
  • Only works for forms, not tables
  • Requries an extension plugin
Status: Not recommended

Detailed steps needed:

Note: This solution has not been tried (though it should work)

  • Create the class org.eclipse.minicrm.ui.swing.SwingUncachedIconLocator as described above.
  • Create a plugin project org.eclipse.minicrm.scout.rt.ui.swing
  • Make sure the package org.eclipse.minicrm.scout.rt.ui.swing.form.fields is exported in MANIFEST.MF
  • Create a class org.eclipse.minicrm.scout.rt.ui.swing.form.fields.SwingScoutUncachedImageField with the following content:
public class SwingScoutUncachedImageField extends SwingScoutImageField {
  private SwingUncachedIconLocator m_uncachedIconLocator;

  protected SwingUncachedIconLocator getUncachedIconLocator() {
    if (m_uncachedIconLocator == null) {
      m_uncachedIconLocator = new SwingUncachedIconLocator(getSwingEnvironment().getScoutSession().getIconLocator());
    }
    return m_uncachedIconLocator;
  }

  @Override
  protected void setImageFromScout(String imageId, Object image) {
    if (image == null) {
      if (imageId != null) {
        // try to use uncached image
        image = getUncachedIconLocator().getImage(imageId);
        if (image == null) {
          // as fallback, use cached image
          image = getSwingEnvironment().getImage(imageId);
        }
      }
    }
    getSwingImageViewer().setImage(image);
  }
}
  • In the plugin's plugin.xml file add the following extension point:
<plugin>
   <extension point="org.eclipse.scout.rt.ui.swing.formfields">
      <formField
            scope="global"
            active="true"
            modelClass="org.eclipse.scout.rt.client.ui.form.fields.imagebox.IImageField"
            name="imagefield">
         <uiClass
               class="org.eclipse.minicrm.scout.rt.ui.swing.form.fields.SwingScoutUncachedImageField">
         </uiClass>
      </formField>
   </extension>
</plugin>



.

Overwrite SwingEnvironment.createIconLocator

Synopsis: Overwriting SwingEnvironemtn.createIconLocator to always return a non-caching IconLocator
Advantages:
  • No code redundancies
Disadvantages:
  • All AbstractImageFields become uncached
  • Only works for forms, not tables
Status: Not recommended
Detailed steps needed:
  • Create the class org.eclipse.minicrm.ui.swing.SwingUncachedIconLocator as described above.
  • Add the following method to org.eclipse.minicrm.ui.swing.SwingEnvironment:
  protected SwingIconLocator createIconLocator() {
    return new SwingUncachedIconLocator(getScoutSession().getIconLocator());
  }



.

Extending SwingScoutImageField and overwriting SwingEnvironment.createFormField

Synopsis: Extend
Advantages:
  • No code redundancies
  • No plugin needed
  • Possibility to decide which ImageFields are cached and which aren't
Disadvantages:
  • Additional criteria/rules to decide which fields need to be cached/uncached need to be hard coded into SwingEnvironment
  • Only works for forms, not tables
Status: Not recommended
Detailed steps needed:
  • Create the class org.eclipse.minicrm.ui.swing.SwingUncachedIconLocator as described above.
  • Create a class org.eclipse.minicrm.ui.swing.SwingScoutUncachedImageField with the following content:
public class SwingScoutUncachedImageField extends SwingScoutImageField {
  private SwingUncachedIconLocator m_uncachedIconLocator;

  protected SwingUncachedIconLocator getUncachedIconLocator() {
    if (m_uncachedIconLocator == null) {
      m_uncachedIconLocator = new SwingUncachedIconLocator(getSwingEnvironment().getScoutSession().getIconLocator());
    }
    return m_uncachedIconLocator;
  }

  @Override
  protected void setImageFromScout(String imageId, Object image) {
    if (image == null) {
      if (imageId != null) {
        // try to use uncached image
        image = getUncachedIconLocator().getImage(imageId);
        if (image == null) {
          // as fallback, use cached image
          image = getSwingEnvironment().getImage(imageId);
        }
      }
    }
    getSwingImageViewer().setImage(image);
  }
}
  • Add the following method to org.eclipse.minicrm.ui.swing.SwingEnvironment:
@Override
  public ISwingScoutFormField<?> createFormField(JComponent parent, IFormField field) {
    if (field instanceof IImageField /* && add further conditions here */) {
      SwingScoutUncachedImageField ui = new SwingScoutUncachedImageField();
      ui.createField((IImageField) field, this);
      decorate(field, ui);
      return ui;
    }
    return super.createFormField(parent, field);
  }



.

Use ImageField.setImage() instead of ImageField.setImageId()

Synopsis: Instead of passing the loaded image to an IconProviderService and then calling setImageId(imageId) on the ImageField, the content is directly passed to the ImageField using setImage(content)
Advantages:
  • No code redundancies
  • renderer independent (SWT/Swing)
Disadvantages:
  • Only works for forms, not tables
Status: Recommended
Detailed steps needed:
  • Add byte[] member to form class:
public class MyForm extends AbstractForm {
  private byte[] content;
  • Setting image from database:
      content = SERVICES.getService(IMyProcessService.class).loadImage(keyValue);
      getImageField().setImage(content);
  • Setting image from file:
  public void updateImageFile(File file) throws ProcessingException {
    if (file != null) {
      content = new byte[(int) file.length()];
      DataInputStream dis;
      try {
        dis = new DataInputStream(new FileInputStream(file));
        dis.readFully(content);
        dis.close();

        getImageField().setImage(content);
      }
      catch (FileNotFoundException e) {
        throw new ProcessingException(e.getMessage());
      }
      catch (IOException e) {
        throw new ProcessingException(e.getMessage());
      }
    }
  }
  • Clearing image:
      content = null;
      getImageField().setImage(content);



.

Not re-using iconId

Synopsis: Instead of using the key of the record showing on the form as imageId, the imageId is increased every time the image content changes (or even whenever the image is set).
Advantages:
  • No code modification needed
  • Works for forms and tables
Disadvantages:
  • Cache fills up quickly (and in many cases unnecessarily); no "garbage collection" process
Status: Not recommended
Detailed steps needed:
  • A static Long member would need to be added to the form class. This member would need to be increased and appended to the imageId whenever the image is updated, the remaining code would stay unchanged compared to the solutions using an UncachedImageField.
      String imageId = "Image-" + keyValue + staticCounter++;
      content = SERVICES.getService(IMyProcessService.class).loadImage(imageId);
      org.eclipse.minicrm.client.Activator.getDefault().cacheImage(imageId, content);
      getUncachedImageField().setImageId(imageId);



.

Use MD5 checksum as iconId

Synopsis: Instead of using a constantly increasing imageId, independently of whether the image changed or not the MD5 checksum over the image data is used. This checksum only changes if the image content changed as well. The probability for identical MD5 checksums for different images is negligable.
Advantages:
  • No code modifications needed
  • Works for forms and tables
  • New cache entries only created when needed
Disadvantages:
  • Cache still fills up (though only when necessary)
Status: Recommended
Detailed steps needed:
  • Setting an image on a form:
      if (content != null) {
        String md5;
        try {
          md5 = Base64Utility.encode(EncryptionUtility.signMD5(content));
        }
        catch (NoSuchAlgorithmException e) {
          throw new ProcessingException("Could not create MD5 hash for person portrait: " + e.getMessage());
        }
        String imageId = md5;
        content = SERVICES.getService(IMyProcessService.class).loadImage(imageId);
        org.eclipse.minicrm.client.Activator.getDefault().cacheImage(imageId, content);
        getUncachedImageField().setImageId(imageId);
      } else {
        getUncachedImageField().setImageId(null);
      }
  • Setting an image in a table column: Instead of the imageId, the MD5 checksum is returned from the OutlineService:
      @Override
      protected void execDecorateCell(Cell cell, ITableRow row) throws ProcessingException {
        String keyValue = getKeyValueColumn().getValue(row);
        String imageId = getValue(row); // this is the MD5 checksum
        if (!org.eclipse.minicrm.client.Activator.getDefault().isImageCached(imageId)) {
          byte[] content = SERVICES.getService(IMyProcessService.class).loadImage(keyValue);
          org.eclipse.minicrm.client.Activator.getDefault().cacheImage(imageId, content);
        }
        cell.setIconId(imageId);
        cell.setText(null);
      }



.

Recommended solution

Synopsis: The recommended solution is to combine two of the methods above:
Advantages:
  • No code modifications needed
  • Form images are not cached at all
  • Table images are only cached when they change
  • Works for forms and tables
Disadvantages:
  • None



.

.

.

.

.

.