Dienstag, 24. August 2010

Selbstbaukasten – CCD Gradanzeige für Eclipse

Der Selbstbaukasten beschreibt wie ein Eclipse-Plugin für die CCD Gradanzeige in Eclipse erstellt werden kann. Die CCD Gradanzeige veranschaulicht in welcher Entwicklungsstufe sich ein CCD-Entwickler befindet. Die Gradanzeige ist in Farben unterteilt. Ausgehend vom schwarzen Grad 0 bis hin zum weißen Grad 6 beachtet der CCD-Entwickler die in der jeweiligen Gradstufe und den davorliegenden Gradstufen zugeordneten CCD Prinzipien und Praktiken.

Das Gradsystem drückt aus, dass ein Clean Code Developer ein inneres Wertesystem besitzt an dem er stetig arbeitet um schrittweise und nachhaltig bessere Softwarequalität zu erzeugen. Die CCD Gradanzeige ist konform der Technik des CCD Armbandes anzuwenden. Weitere Informationen zu CCD und den CCD Armbändern sind auf der Internetseite http://www.clean-code-developer.de/ zu finden.

Für die Programmierung des Eclipse-Plugins ist zunächst eine aktuelle Version von „Eclipse for RCP and RAP Developers“ zu installieren. Eine Installation von Java SE 6 wird vorausgesetzt. Nach der Installation von Eclipse ist ein Eclipse Workspace (zum Beispiel \ccd-level-plugin) anzulegen. Nach dem Starten von Eclipse und Selektion des neu angelegten Workspaces legt man ein „Plug-In Project“ an.

Selektion des Projekttyps "Plug-in Project"  
Next > Schaltfläche betätigen

Projektname "CCDLevelPlugin" eingeben
Next > Schaltfläche betätigen

Activator-Name ändern: ccd.level.plugin.Activator
 Next > Schaltfläche betätigen

Template Checkbox deselektieren

Finish Schaltfläche betätigen

Das Plugin-Projekt ist angelegt worden und nun zu vervollständigen.

Zunächst sind im "src" Ordner des Plugin-Projektes drei neue Java-Packages anzulegen:

ccd.level.plugin.i18n 
ccd.level.plugin.preferences 
ccd.level.plugin.toolbar

Im ccd.level.plugin Package ist die Klasse Activator durch folgenden Quellcode zu ersetzen:

import org.eclipse.jface.resource.ImageDescriptor;
import org.eclipse.ui.plugin.AbstractUIPlugin;
import org.osgi.framework.BundleContext;

public class Activator extends AbstractUIPlugin {

    public static final String PLUGIN_ID = "ccd.level.plugin";

    private static Activator plugin;
   
    public void start(BundleContext context) throws Exception {
        super.start(context);
        plugin = this;
    }

    public void stop(BundleContext context) throws Exception {
        plugin = null;
        super.stop(context);
    }

    public static Activator getDefault() {
        return plugin;
    }

    public static ImageDescriptor getImageDescriptor(String path) {
        return imageDescriptorFromPlugin(PLUGIN_ID, path);
    }
}

Im ccd.level.plugin.i18n Package ist die Klasse Messages anzulegen:

import org.eclipse.osgi.util.NLS;

public class Messages extends NLS {
   
    private static final String BUNDLE_NAME = "ccd.level.plugin.i18n.messages";
    public static String $LABEL_BLACK_LEVEL;
    public static String $LABEL_BLUE_LEVEL;
    public static String $LABEL_CCD_LEVELS;
    public static String $LABEL_GREEN_LEVEL;
    public static String $LABEL_ORANGE_LEVEL;
    public static String $LABEL_RED_LEVEL;
    public static String $LABEL_WHITE_LEVEL;
    public static String $LABEL_YELLOW_LEVEL;
    public static String $TOOLTIP_BLACK_LEVEL;
    public static String $TOOLTIP_BLUE_LEVEL;
    public static String $TOOLTIP_GREEN_LEVEL;
    public static String $TOOLTIP_ORANGE_LEVEL;
    public static String $TOOLTIP_RED_LEVEL;
    public static String $TOOLTIP_WHITE_LEVEL;
    public static String $TOOLTIP_YELLOW_LEVEL;
    static {
       
        NLS.initializeMessages(BUNDLE_NAME, Messages.class);
    }

    private Messages() {/* prevents instantiation */}
}

Nach dem Anlegen der Klasse sind folgende Messages-Dateien anzulegen:

messages.properties

$LABEL_BLACK_LEVEL=&Schwarzer Grad
$LABEL_BLUE_LEVEL=&Blauer Grad
$LABEL_CCD_LEVELS=Clean Code Developer Grade
$LABEL_GREEN_LEVEL=G&r\u00FCner Grad
$LABEL_ORANGE_LEVEL=&Oranger Grad
$LABEL_RED_LEVEL=&Roter Grad
$LABEL_WHITE_LEVEL=&Wei\u00DFer Grad
$LABEL_YELLOW_LEVEL=&Gelber Grad
$TOOLTIP_BLACK_LEVEL=CCD: Schwarzer Grad
$TOOLTIP_BLUE_LEVEL=CCD: Blauer Grad
$TOOLTIP_GREEN_LEVEL=CCD: Gr\u00FCner Grad
$TOOLTIP_ORANGE_LEVEL=CCD: Oranger Grad
$TOOLTIP_RED_LEVEL=CCD: Roter Grad
$TOOLTIP_WHITE_LEVEL=CCD: Wei\u00DFer Grad
$TOOLTIP_YELLOW_LEVEL=CCD: Gelber Grad

messages_de.properties

$LABEL_BLACK_LEVEL=&Schwarzer Grad
$LABEL_BLUE_LEVEL=&Blauer Grad
$LABEL_CCD_LEVELS=Clean Code Developer Grade
$LABEL_GREEN_LEVEL=G&r\u00FCner Grad
$LABEL_ORANGE_LEVEL=&Oranger Grad
$LABEL_RED_LEVEL=&Roter Grad
$LABEL_WHITE_LEVEL=&Wei\u00DFer Grad
$LABEL_YELLOW_LEVEL=&Gelber Grad
$TOOLTIP_BLACK_LEVEL=CCD: Schwarzer Grad
$TOOLTIP_BLUE_LEVEL=CCD: Blauer Grad
$TOOLTIP_GREEN_LEVEL=CCD: Gr\u00FCner Grad
$TOOLTIP_ORANGE_LEVEL=CCD: Oranger Grad
$TOOLTIP_RED_LEVEL=CCD: Roter Grad
$TOOLTIP_WHITE_LEVEL=CCD: Wei\u00DFer Grad
$TOOLTIP_YELLOW_LEVEL=CCD: Gelber Grad

messages_en.properties

$LABEL_BLACK_LEVEL=&Black Level
$LABEL_BLUE_LEVEL=B&lue Level
$LABEL_CCD_LEVELS=Clean Code Developer Levels
$LABEL_GREEN_LEVEL=&Green Level
$LABEL_ORANGE_LEVEL=&Orange Level
$LABEL_RED_LEVEL=&Red Level
$LABEL_WHITE_LEVEL=&White Level
$LABEL_YELLOW_LEVEL=&Yellow Level
$TOOLTIP_BLACK_LEVEL=CCD: Black Level
$TOOLTIP_BLUE_LEVEL=CCD: Blue Level
$TOOLTIP_GREEN_LEVEL=CCD: Green Level
$TOOLTIP_ORANGE_LEVEL=CCD: Orange Level
$TOOLTIP_RED_LEVEL=CCD: Red Level
$TOOLTIP_WHITE_LEVEL=CCD: White Level
$TOOLTIP_YELLOW_LEVEL=CCD: Yellow Level

Im ccd.level.plugin.preferences Package ist die Enumeration CcdGrad anzulegen:

import ccd.level.plugin.i18n.Messages;

public enum CcdGrad {

    BLACK("0", "ccd-level-black.png", Messages.$TOOLTIP_BLACK_LEVEL),
    RED("1", "ccd-level-red.png", Messages.$TOOLTIP_RED_LEVEL),
    ORANGE( "2", "ccd-level-orange.png", Messages.$TOOLTIP_ORANGE_LEVEL),
    YELLOW("3", "ccd-level-yellow.png", Messages.$TOOLTIP_YELLOW_LEVEL),
    GREEN("4", "ccd-level-green.png", Messages.$TOOLTIP_GREEN_LEVEL),
    BLUE("5", "ccd-level-blue.png", Messages.$TOOLTIP_BLUE_LEVEL),
    WHITE("6", "ccd-level-white.png", Messages.$TOOLTIP_WHITE_LEVEL);
      
    private String tooltip;
    private String imageName;
    private String id;

    private CcdGrad(String id, String imageName, String tooltip) {
      
        this.id = id;
        this.imageName = imageName;
        this.tooltip = tooltip;
    }

    public String getTooltip() {
        return tooltip;
    }

    public String getImageName() {
        return imageName;
    }

    public String getId() {
        return id;
    }
}

Im ccd.level.plugin.preferences Package ist die Klasse PreferenceConstants anzulegen:

public class PreferenceConstants {
   
    private PreferenceConstants() {/* prevent instantiation */}
       
    public static final String LEVEL_CHOICE = "ccdLevel";
}

Im ccd.level.plugin.preferences Package ist die Klasse PreferenceInitializer anzulegen:

import ccd.level.plugin.Activator;
import org.eclipse.core.runtime.preferences.AbstractPreferenceInitializer;
import org.eclipse.jface.preference.IPreferenceStore;

public class PreferenceInitializer extends AbstractPreferenceInitializer {

    public void initializeDefaultPreferences() {
       
        final IPreferenceStore store = Activator.getDefault().getPreferenceStore();
        store.setDefault(PreferenceConstants.LEVEL_CHOICE,
                CcdGrad.BLACK.getId());
    }
}

Im ccd.level.plugin.preferences Package ist die Klasse PreferencePage anzulegen:

import ccd.level.plugin.Activator;
import ccd.level.plugin.i18n.Messages;
import org.eclipse.jface.preference.*;
import org.eclipse.ui.IWorkbenchPreferencePage;
import org.eclipse.ui.IWorkbench;

public class PreferencePage extends FieldEditorPreferencePage implements
                                                        IWorkbenchPreferencePage {
   
    private static final String TITLE_CCD_GRADE = Messages.$LABEL_CCD_LEVELS;
    private static final String LABEL_WHITE_LEVEL = Messages.$LABEL_WHITE_LEVEL;
    private static final String LABEL_BLUE_LEVEL = Messages.$LABEL_BLUE_LEVEL;
    private static final String LABEL_GREEN_LEVEL = Messages.$LABEL_GREEN_LEVEL;
    private static final String LABEL_YELLOW_LEVEL = Messages.$LABEL_YELLOW_LEVEL;
    private static final String LABEL_ORANGE_LEVEL = Messages.$LABEL_ORANGE_LEVEL;
    private static final String LABEL_RED_LEVEL = Messages.$LABEL_RED_LEVEL;
    private static final String LABEL_BLACK_LEVEL = Messages.$LABEL_BLACK_LEVEL;

    public PreferencePage() {
        super(GRID);
        setPreferenceStore(Activator.getDefault().getPreferenceStore());       
    }

    public void createFieldEditors() {

        addField(new RadioGroupFieldEditor(PreferenceConstants.LEVEL_CHOICE,
                          TITLE_CCD_GRADE, 1, new String[][] {
                        { LABEL_BLACK_LEVEL, CcdGrad.BLACK.getId() },
                        { LABEL_RED_LEVEL, CcdGrad.RED.getId()},
                        { LABEL_ORANGE_LEVEL, CcdGrad.ORANGE.getId() },
                        { LABEL_YELLOW_LEVEL, CcdGrad.YELLOW.getId() },
                        { LABEL_GREEN_LEVEL, CcdGrad.GREEN.getId() },
                        { LABEL_BLUE_LEVEL, CcdGrad.BLUE.getId()},
                        { LABEL_WHITE_LEVEL, CcdGrad.WHITE.getId() }},
                        getFieldEditorParent()));
    }
       
    public void init(IWorkbench workbench) { /* nothing to override */}
}

Im ccd.level.plugin.toolbar Package ist die Klasse ToolBarExtensionItemException anzulegen:

public class ToolBarExtensionItemException extends RuntimeException {       

    private static final long serialVersionUID = 1L;

    public ToolBarExtensionItemException(final Exception exception) {
        super(exception);
    }
}

Im ccd.level.plugin.toolbar Package ist die Klasse ToolBarExtensionItem anzulegen:

import java.io.InputStream;
import ccd.level.plugin.Activator;
import ccd.level.plugin.preferences.CcdGrad;
import ccd.level.plugin.preferences.PreferenceConstants;
import org.eclipse.swt.SWT;
import org.eclipse.swt.graphics.Image;
import org.eclipse.swt.graphics.ImageData;
import org.eclipse.swt.widgets.Composite;
import org.eclipse.swt.widgets.Control;
import org.eclipse.swt.widgets.Label;

final class ToolBarExtensionItem {

    private final class ImageItem {
       
        private final String tooltip;
        private final Image image;
       
        public ImageItem(final Image image, final String tooltip) {
           
            this.image = image;
            this.tooltip = tooltip;
        }

        public String getTooltip() {
            return tooltip;
        }

        public Image getImage() {
            return image;
        }               
    }
   
    private final ImageItem [] imageItems = new ImageItem [] {
           
        new ImageItem(getImage(CcdGrad.BLACK.getImageName()), CcdGrad.BLACK.getTooltip()),
        new ImageItem(getImage(CcdGrad.RED.getImageName()), CcdGrad.RED.getTooltip()),
        new ImageItem(getImage(CcdGrad.ORANGE.getImageName()), CcdGrad.ORANGE.getTooltip()),
        new ImageItem(getImage(CcdGrad.YELLOW.getImageName()), CcdGrad.YELLOW.getTooltip()),
        new ImageItem(getImage(CcdGrad.GREEN.getImageName()), CcdGrad.GREEN.getTooltip()),
        new ImageItem(getImage(CcdGrad.BLUE.getImageName()), CcdGrad.BLUE.getTooltip()),
        new ImageItem(getImage(CcdGrad.WHITE.getImageName()), CcdGrad.WHITE.getTooltip())
    };
   
    private int currentLevel = 0;

    public static ToolBarExtensionItem newInstance() {
        return new ToolBarExtensionItem();
    }
   
    private ToolBarExtensionItem() {
       
        initialize();       
    }

    private void initialize() {
       
        setLevel();       
    }

    private void setLevel() {
       
        try {
           
            final String levelStr = Activator.getDefault().getPreferenceStore().
                                                                              getString(PreferenceConstants.LEVEL_CHOICE);           
            if(levelStr != null) {
               
                final int level = Integer.parseInt(levelStr);
                currentLevel = level >= 0 && level <= 6 ? level : 0;
            }
        }
        catch(Exception ex) {
           
            throw new ToolBarExtensionItemException(ex);
        }               
    }
   
    public Control getItem(final Composite parent) {
               
        return getItemLabel(parent);
    }

    private Label getItemLabel(final Composite parent) {
       
        Label label = new Label(parent, SWT.BORDER);   
        label.setImage(getCurrentImage());
        label.setToolTipText(getCurrentTooltip());       
       
        return label;
    }
       
    private Image getCurrentImage() {
       
        return imageItems[currentLevel].getImage();
    }
   
    private String getCurrentTooltip() {
       
        return imageItems[currentLevel].getTooltip();
    }
   
    private Image getImage(final String imageName) {
   
        InputStream sourceStream = null;
        ImageData source = null;
        try {
           
            sourceStream = ToolBarExtensionItem.class.getResourceAsStream(imageName);
            source = new ImageData(sourceStream);
                       
            try {
           
                if(sourceStream != null) {
                   
                    sourceStream.close();
                }
            }
            catch (Exception ex) {
               
                throw new ToolBarExtensionItemException(ex);
            }
        }
        catch(Exception ex) {
           
            throw new ToolBarExtensionItemException(ex);
        }
                       
        return new Image(null, source);
    }
}

Im ccd.level.plugin.toolbar Package ist die Klasse ToolBarExtension anzulegen:

import org.eclipse.swt.widgets.Composite;
import org.eclipse.swt.widgets.Control;
import org.eclipse.ui.menus.WorkbenchWindowControlContribution;

public class ToolBarExtension extends WorkbenchWindowControlContribution {

    protected Control createControl(final Composite parent) {
               
        final ToolBarExtensionItem toolbarExtensionItem = ToolBarExtensionItem.newInstance();   
        return toolbarExtensionItem.getItem(parent);
    }
}

Die folgenden Images in das Package ccd.level.plugin.toolbar einfügen (rechte Maustaste auf dem Bild und im Menü "Grafik speichern unter..." wählen:













Nach dem Speichern der Bilder im Package ccd.level.plugin.toolbar in Eclipse das Package selektieren und anschliessend die F5-Taste betätigen. Die Bilder sollten nun im Package aufgelistet werden.

Im Eclipse-Projekt unterhalb der build.properties die Datei plugin.xml anlegen:

<?xml version="1.0" encoding="UTF-8"?>
<?eclipse version="3.0"?>
<plugin>
<extension
      id="ccd.level.plugin.menu"
      name="%extension.name"
      point="org.eclipse.ui.menus">
  <menuContribution locationURI="toolbar:org.eclipse.ui.trim.status">
    <toolbar id="ccd.level.plugin.toolbar">
      <control
            class="ccd.level.plugin.toolbar.ToolBarExtension"
            id="ccd.level.plugin.toolbar.extension">
      </control>
    </toolbar>
  </menuContribution>
</extension>
<extension
      id="ccd.level.plugin.preferences.pages"
      name="%extension.name.preferences.pages"
      point="org.eclipse.ui.preferencePages">
   <page
         class="ccd.level.plugin.preferences.PreferencePage"
         id="ccd.level.plugin.preferences.PreferencePage"
         name="CCD">
   </page>
</extension>
<extension
      id="ccd.level.plugin.preferences"
      name="%extension.name.preferences"
      point="org.eclipse.core.runtime.preferences">
   <initializer
         class="ccd.level.plugin.preferences.PreferenceInitializer">
   </initializer>
</extension>
</plugin>

Die Datei MANIFEST.MF im Verzeichnis META-INF mit folgendem Inhalt ersetzen:

Manifest-Version: 1.0
Bundle-ManifestVersion: 2
Bundle-Name: %Bundle-Name
Bundle-SymbolicName: ccd.level.plugin;singleton:=true
Bundle-Version: 1.0.0.beta
Bundle-Activator: ccd.level.plugin.Activator
Bundle-Vendor: %Bundle-Vendor
Require-Bundle: org.eclipse.ui,
 org.eclipse.core.runtime,
 org.eclipse.core.resources
Bundle-RequiredExecutionEnvironment: JavaSE-1.6
Bundle-ActivationPolicy: lazy

Unterhalb des Ordners META-INF legt man nun den Ordner OSGI-INF mit einem weiteren Unterverzeichnis I10n an In dem Unterverzeichnis I10n erstellt man die Datei bundle.properties mit folgendem Inhalt:

#Properties file for ccd.level.plugin
Bundle-Vendor = Clean Code Developer
Bundle-Name = CCD-Level
extension.name = ccd.level.plugin.menu
extension.name.preferences.pages = ccd.level.plugin.preferences.pages
extension.name.preferences = ccd.level.plugin.preferences

Anschliessend ersetzt man den Inhalt der build.properties mit folgenden Einträgen:

source.. = src/
output.. = bin/
bin.includes = META-INF/,\
               .,\
               plugin.xml,\
               OSGI-INF/

Das Plugin-Projekt ist nun fertiggestellt und sollte folgendes aussehen haben:

Package Explorer Ansicht in Eclipse

Das Plugin kann ausgeführt werden, indem man die plugin.xml selektiert und im "Overview" Tab-Reiter den Link "Launch an Eclipse application" selektiert.

Eine neue Eclipse Workbench wird nach dem Betätigen des Links gestartet und die Gradanzeige erscheint in einer der beiden Ecken der Eclipse Statuszeile.

In den Preferences kann der Grad eingestellt werden. Nach dem Einstellen des Grades muss Eclipse allerdings neu gestartet werden, sodass der Grad mit der neuen Einstellung angezeigt wird.






Gradanzeige und Einstellung des Grades
Das Plugin kann nun mit dem Export Wizzard exportiert und in eine andere Eclipse Distribution installiert werden.Das Plugin für die Gradanzeige ist ein Auszug aus dem Feature der CCD-Workbench, welche weitere Eclipse-Plugins (CCD Inspector und CCD Analyzer sowie ein Cheat Sheet) beinhaltet.


Das Plugin für die CCD Gradanzeige darf frei verwendet werden! Der Quellcode kann aus dem Blog-Eintrag durch Selektion mit der Maustaste und der Wahl des Menüpunktes "Kopieren" in die Zwischenablage kopiert werden. Von dort aus ist der Quellcode in die in dem Eclipse Plugin-Projekt angelegten Dateien einzufügen. Der Rechtshinweis des Java Blogs für Clean Code Developer ist bei der Verwendung des Quellcodes zu beachten.