Archive for July, 2009

Deprecate certain extension points to guide developers?

Wednesday, July 29th, 2009

Given the discussion about E4 and the evolution of Eclipse I’m wondering if it would make sense to flag certain Eclipse extension points as deprecated and add a warning to the PDE tooling if someone uses these extension points. This would help developes to do “the right thing”.

This question came to me while watching The Myth of the Genius Programmer. Björn quoted from this presentation in this post Criticism is not evil . In this video it was explained that tools matter in the sense that they guide new developer in a certain direction.

For example I believe that actions could be marked as deprecated. I see still a lot of questions which relates to actions in the newsgroup and I believe that commands are superior to actions. I know that the description indicates that “org.eclipse.ui.menus” can be used but perhaps more guidance would help?

Any opinions on this?

Update I submitted Bug 285034 for the enhancement in PDE / UI for the warning.

 

Eclipse Papercut #4 – Modifying Eclipse PDE default launch configuration

Monday, July 27th, 2009

In this episode of the Eclipse Papercut series I will change the default PDE launch configuration. During this process I show how to get and modify Eclipse PDE code.

Lets define the papercut:

One thing I always have to do during plugin and Eclipse RCP development is to make the following settings in the launch configuration:

  1. add -consoleLog in the arguments tab
  2. Select the flag “Validate plug-in automatically prio to launching” on the plug-ins tab

This papercut has two dimensions.

The first one is that for everybody who knows about these settings it is not a problem to set them up. It is just time-consuming and error prone. Sometimes I’m searching why something does not work and then I’m banging my head because I forgot to set -consoleLog.

The second dimension is that this default launch configuration makes it harder for new plugin and Eclipse RCP developers to get started. I frequently see questions in the Eclipse newsgroup and other places which can be answered by: “please set -consoleLog” or “press the Validate button”.

So I seek to change the current behavior.

Adding “-consoleLog” is easy. Go to preference, select the target platform, select “Edit” and add -consoleLog as a argument.

target10

target20

Of course this does not help the new developers but I come back to this later in this post.

To set the “Validate plug-in automatically prio to launching” flag we have to look into Eclipse PDE code.

A launch configuration is contributed by the extension point “org.eclipse.debug.core.launchConfigurationTypes”. To search through your existing plugins import your plugins into a new workspace. A plugin search reveals that “org.eclipse.pde.ui” contributes such an extension.

Eclipse PDE can be found in the Eclipse cvs via the repository path “/cvsroot/eclipse”. Search here for the folder “pde” which contains the pde plugins. Checkout the plugin “org.eclipse.pde.ui”.

Here we find quickly the right extension point.

pde10

After some pocking around the code and some debugging I found the class “AbstractPluginBlock” and the method “setDefaults”. The change is trivial:

public void setDefaults(ILaunchConfigurationWorkingCopy config) {
	config.setAttribute(IPDELauncherConstants.INCLUDE_OPTIONAL, true);
	config.setAttribute(IPDELauncherConstants.AUTOMATIC_ADD, true);
	config.setAttribute(IPDELauncherConstants.AUTOMATIC_VALIDATE, true); // This has changed from false to true
	config.setAttribute(IPDELauncherConstants.SHOW_SELECTED_ONLY, false);
}

If you now run Eclipse with this adjusted plugin the flag “Validate…” will be flagged for a new runtime configuration.

Now lets return to the addtion of “-consoleLog” to the target platform. I argued earlier that the absence of “-consoleLog” makes it harder for beginner to get started with plugin and RCP development. Changing the target platform is not trivial for starters, so the better solution was if was part of the standard code.

We find the relevant code easily with a text seach for an existing launch parameter, e.g. “-arch”. The responsible class is “LaunchArgumentsHelper” and the method getInitialProgramArguments(). We add “-consoleLog” to the following line:


StringBuffer buffer = new StringBuffer("-os ${target.os} -ws ${target.ws} -arch ${target.arch} -nl ${target.nl} -consoleLog"); //$NON-NLS-1$

Thats it. By deploying the plugin into our Eclipse IDE we have the changed behavior.

I have submitted bugs and attached the patches. Bug Report for “Validate plug-in automatically prio to launching” and Bug Report for -consoleLog.

I hope that these patches will get accepted. If they would be accepted I believe it would be a little easier for new developers to get started.

If someone finds a reason why these settings should not always be the default I would suggest a new PDE preference page which contains the settings for these launch parameters.

 

Hello Planet Eclipse,

Friday, July 24th, 2009

My blog posts are now aggregated on Planet Eclipse and I want to follow the ancient tradition of introducing myself.

I try to help others to learn the Eclipse platform by providing Eclipse Tutorials, for example about Eclipse RCP. More about me can be found here.

While the intent of my articles is to cover larger and hopefully timeless topics, my blog posts cover less large and less timeless ;-) topics related to Eclipse. The remaining things I tweet at Twitter.

Currently I’m writing the 7 Papercuts in Eclipse series in which I discuss usability issues in the Eclipse platform and how to overcome them.

I hope that you will find my posts enjoyable and useful.

 

Java Performance with Strings and StringBuilder

Sunday, July 19th, 2009

Strings are very frequently used in Java programs. This blog post tries to explain what a programmer needs to consider from a performance point of view. It will also explain in what situations you should use StringBuilder instead of String.

Strings in Java are immutable. If you look at the source code of String you find that java.lang.String has the following properties.

/** The value is used for character storage. */
    private final char value[];

 /** The offset is the first index of the storage that is used. */
    private final int offset;

 /** The count is the number of characters in the String. */
    private final int count;

The array is used to store the values of this String.

As Strings are immutable they can be freely shared. This property is utilized in the method substring(). The method substring will use a reference to the same String and only change the offset and the lenght value for the String. The same string is in this case used several times.

Therefore using substring requires only a constant amount of time (and almost no additional memory) and can be freely used.

The operation concat() (which is called by the + operator) combines two Strings. This method has to copy the characters of the two Strings and therefore takes time and extra space which is propotional to the length of the two strings .

The object StringBuilder has a more effectly way of concatenate Strings. It works similar to the class ArrayList by allocating a predefined array for storing the characters and keeps track of the used space. Every time the space is exceeded then it will extend the available capacity).

Does this means that you always have to use StringBuilder if you are concatening strings?

No. Of course if in your program you combine only a few times String the runtime overhead is normally not relevant for the overall performance.

But of course if you combine frequently strings in your program you should switch to StringBuilder.

 

Eclipse Papercut #3 – Plugin to find unused methods

Wednesday, July 15th, 2009

In this episode of Eclipse Papercuts we will look at how we can analyse our own code to find “dead” code in your projects. We will write a plug-in to find methods which are not called (within the workspace).

methodCalls

Of course if you are a framework developer all your exported public methods are API and you can hardly change it. But in a lot of cases the development team has all code in one workspace. In this case it should be easy to identify dead code easily. Currently you have to select each method and select “Open Call Hierachy”.

This calls of course for a simpler solution, lets solve a papercut.

Create a Plug-in Project “de.vogella.jdt.codeanalysis” .

Define the following model class which will store the results of the calucation.


package de.vogella.jdt.infoview.model;

import org.eclipse.jdt.core.ICompilationUnit;
import org.eclipse.jdt.core.IMethod;

public class MethodInformation {
private final String methodName;
private final int numberOfCalls;
private final ICompilationUnit cu;
private final IMethod method;

public MethodInformation(String methodName, int numberOfCalls,
ICompilationUnit cu, IMethod method) {
this.methodName = methodName;
this.numberOfCalls = numberOfCalls;
this.cu = cu;
this.method = method;
}

/**
* @return the method
*/
public String getMethodName() {
return methodName;
}

/**
* @return the numberOfCalls
*/
public int getNumberOfCalls() {
return numberOfCalls;
}

/**
* @return the cu
*/
public ICompilationUnit getResource() {
return cu;
}

/**
* @return the method
*/
public IMethod getMethod() {
return method;
}

}

Define a Eclipse command “de.vogella.jdt.codeanalysis.calculateUsage” with the default handler “de.vogella.jdt.codeanalysis.handler.CalculateUsage”.

Create these two helper classes with will search through a given Java project. The usage of the JDT functionality is explained in Eclipse JDT.


package de.vogella.jdt.codeanalysis.analysis;

import java.util.ArrayList;
import java.util.List;

import org.eclipse.core.runtime.CoreException;
import org.eclipse.jdt.core.ICompilationUnit;
import org.eclipse.jdt.core.IJavaProject;
import org.eclipse.jdt.core.IMethod;
import org.eclipse.jdt.core.IPackageFragment;
import org.eclipse.jdt.core.IPackageFragmentRoot;
import org.eclipse.jdt.core.IType;
import org.eclipse.jdt.core.search.IJavaSearchConstants;
import org.eclipse.jdt.core.search.IJavaSearchScope;
import org.eclipse.jdt.core.search.SearchEngine;
import org.eclipse.jdt.core.search.SearchParticipant;
import org.eclipse.jdt.core.search.SearchPattern;

import de.vogella.jdt.codeanalysis.model.MethodInformation;

public class CodeAnalysis {

	public static List<MethodInformation> calculate(IJavaProject project) {
		List<MethodInformation> list = new ArrayList<MethodInformation>();
			try {
				if (project.isOpen()) {

					IPackageFragment[] packages = project
							.getPackageFragments();
					// parse(JavaCore.create(project));
					for (IPackageFragment mypackage : packages) {
						if (mypackage.getKind() == IPackageFragmentRoot.K_SOURCE) {
							for (ICompilationUnit unit : mypackage
									.getCompilationUnits()) {
								IType[] types = unit.getTypes();
								for (int i = 0; i < types.length; i++) {
									IType type = types[i];
									IMethod[] methods = type.getMethods();
									for (int j = 0; j < methods.length; j++) {
										IMethod method = methods[j];
										if (!method.isMainMethod()) {
											int number = performIMethodSearch(method);

											if (number == 0) {
												MethodInformation metric = new MethodInformation(
														method.getElementName(),
														number, unit, method);
												list.add(metric);
											}

										}

									}

								}

							}
						}

					}
				}
			} catch (CoreException e) {
				e.printStackTrace();
			}
		return list;
	}

	private static int performIMethodSearch(IMethod method)
			throws CoreException {
		SearchPattern pattern = SearchPattern.createPattern(method,
				IJavaSearchConstants.REFERENCES);
		IJavaSearchScope scope = SearchEngine.createWorkspaceScope();
		MySearchRequestor requestor = new MySearchRequestor();
		SearchEngine searchEngine = new SearchEngine();
		searchEngine.search(pattern, new SearchParticipant[] { SearchEngine
				.getDefaultSearchParticipant() }, scope, requestor, null);
		return requestor.getNumberOfCalls();

	}
}

package de.vogella.jdt.codeanalysis.analysis;

import org.eclipse.core.runtime.CoreException;
import org.eclipse.jdt.core.search.MethodReferenceMatch;
import org.eclipse.jdt.core.search.SearchMatch;
import org.eclipse.jdt.core.search.SearchRequestor;

public class MySearchRequestor extends SearchRequestor {

	private int numberOfCalls = 0;

	@Override
	public void acceptSearchMatch(SearchMatch match) throws CoreException {
		if (match instanceof MethodReferenceMatch) {
//			MethodReferenceMatch methodMatch = (MethodReferenceMatch) match;
//			Object element = methodMatch.getElement();
			numberOfCalls++;
		}
	}

	/**
	 * @return the numberOfCall
	 */
	public int getNumberOfCalls() {
		return numberOfCalls;
	}

}

We create a little View with a Table (and its content and label provider)


package de.vogella.jdt.codeanalysis.views;

import java.util.List;

import org.eclipse.core.runtime.CoreException;
import org.eclipse.jdt.core.ICompilationUnit;
import org.eclipse.jdt.core.IJavaElement;
import org.eclipse.jdt.core.IMethod;
import org.eclipse.jdt.ui.JavaUI;
import org.eclipse.jface.viewers.IStructuredContentProvider;
import org.eclipse.jface.viewers.ITableLabelProvider;
import org.eclipse.jface.viewers.LabelProvider;
import org.eclipse.jface.viewers.TableViewer;
import org.eclipse.jface.viewers.TableViewerColumn;
import org.eclipse.jface.viewers.Viewer;
import org.eclipse.swt.SWT;
import org.eclipse.swt.events.SelectionAdapter;
import org.eclipse.swt.events.SelectionEvent;
import org.eclipse.swt.graphics.Image;
import org.eclipse.swt.widgets.Composite;
import org.eclipse.swt.widgets.Table;
import org.eclipse.ui.IEditorPart;
import org.eclipse.ui.part.ViewPart;

import de.vogella.jdt.codeanalysis.model.MethodInformation;

public class ResultView extends ViewPart {
	public static final String ID = "de.vogella.jdt.codeanalysis.ResultView";
	private TableViewer viewer;

	public void setInput(List<MethodInformation> list) {
		viewer.setInput(list);
	}

	@Override
	public void createPartControl(Composite parent) {
		viewer = new TableViewer(parent, SWT.MULTI | SWT.H_SCROLL
				| SWT.V_SCROLL | SWT.BORDER | SWT.FULL_SELECTION);
		buildTableColumns(viewer);
		viewer.setLabelProvider(new AnalysisLabelProvider());
		viewer.setContentProvider(new AnalysisContentProvider());

		viewer.getTable().addSelectionListener(new SelectionAdapter() {
			@Override
			public void widgetDefaultSelected(SelectionEvent e) {
				Object data = e.item.getData();
				if (!(data instanceof MethodInformation))
					return;
				MethodInformation info = (MethodInformation) data;
				IMethod method = info.getMethod();
				ICompilationUnit cu = info.getResource();
				if (cu == null)
					return;
				try {
					IEditorPart part = JavaUI.openInEditor(cu);
					JavaUI.revealInEditor(part, (IJavaElement) method);
				} catch (CoreException ex) {
					// error handling
				}
			}
		});
	}

	@Override
	public void setFocus() {
	}

	private void buildTableColumns(TableViewer viewer) {
		String[] titles = { "File", "Method", "Number of Calls" };
		int[] bounds = { 100, 100, 100 };

		for (int i = 0; i < titles.length; i++) {
			TableViewerColumn column = new TableViewerColumn(viewer, SWT.NONE);
			column.getColumn().setText(titles[i]);
			column.getColumn().setWidth(bounds[i]);
			column.getColumn().setResizable(true);
			column.getColumn().setMoveable(true);
		}
		Table table = viewer.getTable();
		table.setHeaderVisible(true);
		table.setLinesVisible(true);
	}

	public class AnalysisLabelProvider extends LabelProvider implements
			ITableLabelProvider {

		@Override
		public Image getColumnImage(Object element, int columnIndex) {
			return null;
		}

		@Override
		public String getColumnText(Object element, int columnIndex) {
			MethodInformation metric = (MethodInformation) element;
			switch (columnIndex) {
			case 0:
				return metric.getResource().getElementName();
			case 1:
				return metric.getMethodName();
			default:
				return String.valueOf(metric.getNumberOfCalls());
			}

		}
	}

	public class AnalysisContentProvider implements IStructuredContentProvider {

		@Override
		public Object[] getElements(Object inputElement) {
			List<MethodInformation> list = (List<MethodInformation>) inputElement;
			return list.toArray();
		}

		@Override
		public void dispose() {
		}

		@Override
		public void inputChanged(Viewer viewer, Object oldInput, Object newInput) {
		}

	}

}

Finally we can create the handler:


package de.vogella.jdt.codeanalysis.handler;

import java.util.List;

import org.eclipse.core.commands.AbstractHandler;
import org.eclipse.core.commands.ExecutionEvent;
import org.eclipse.core.commands.ExecutionException;
import org.eclipse.core.runtime.CoreException;
import org.eclipse.core.runtime.IProgressMonitor;
import org.eclipse.core.runtime.IStatus;
import org.eclipse.core.runtime.Status;
import org.eclipse.core.runtime.jobs.Job;
import org.eclipse.jdt.core.IJavaProject;
import org.eclipse.jface.dialogs.MessageDialog;
import org.eclipse.jface.viewers.IStructuredSelection;
import org.eclipse.swt.widgets.Display;
import org.eclipse.ui.PartInitException;
import org.eclipse.ui.handlers.HandlerUtil;

import de.vogella.jdt.codeanalysis.analysis.CodeAnalysis;
import de.vogella.jdt.codeanalysis.model.MethodInformation;
import de.vogella.jdt.codeanalysis.views.ResultView;

public class CalculateUsage extends AbstractHandler {

	@Override
	public Object execute(final ExecutionEvent event) throws ExecutionException {

		IStructuredSelection selection = (IStructuredSelection) HandlerUtil
				.getActiveMenuSelection(event);
		if (selection == null || selection.getFirstElement() == null) {
			// Nothing selected, do nothing
			MessageDialog.openInformation(HandlerUtil.getActiveShell(event),
					"Information", "Please select a project");
			return null;
		}
		final Object firstElement = selection.getFirstElement();
		if (!(firstElement instanceof IJavaProject)) {
			return null;
		}

		final IJavaProject project = (IJavaProject) firstElement;

		try {
			if (!project.isOpen()
					|| !(project.getProject()
							.hasNature("org.eclipse.jdt.core.javanature"))) {
				MessageDialog.openInformation(
						HandlerUtil.getActiveShell(event), "Information",
						"Only works for open Java Projects");
				return null;
			}
		} catch (CoreException e1) {
			return null;
		}

		Job job = new Job("Calculate Usage of methods") {
			@Override
			protected IStatus run(IProgressMonitor monitor) {
				final List<MethodInformation> calculate = CodeAnalysis
						.calculate(project);
				// Open view in the UI thread
				Display.getDefault().asyncExec(new Runnable() {
					public void run() {
						try {
							final ResultView findView = (ResultView) HandlerUtil
									.getActiveWorkbenchWindow(event)
									.getActivePage().showView(ResultView.ID);
							findView.setInput(calculate);
						} catch (PartInitException e) {
							e.printStackTrace();
						}
					}

				});
				return Status.OK_STATUS;
			}

		};
		job.setUser(true);
		job.schedule();

		return null;
	}
}

As a last step add your command to the menu of the package explorer. This is also described Eclipse Plugin Development you results in the following plugin.xml


<?xml version="1.0" encoding="UTF-8"?>
<?eclipse version="3.4"?>
<plugin>
   <extension
         point="org.eclipse.ui.views">
      <view
            class="de.vogella.jdt.codeanalysis.views.ResultView"
            id="de.vogella.jdt.codeanalysis.ResultView"
            name="Analysis Result"
            restorable="true">
      </view>
   </extension>

   <extension
         point="org.eclipse.ui.commands">
      <command
            defaultHandler="de.vogella.jdt.codeanalysis.handler.CalculateUsage"
            id="de.vogella.jdt.codeanalysis.calculateUsage"
            name="Calculate method usage">
      </command>
   </extension>
   <extension
         point="org.eclipse.ui.menus">
      <menuContribution
            locationURI="popup:org.eclipse.jdt.ui.PackageExplorer">
         <command
               commandId="de.vogella.jdt.codeanalysis.calculateUsage"
               label="Calculate method usage"
               style="push">
         </command>
      </menuContribution>
   </extension>

</plugin>

If you now export your plugin into your Eclipse IDE you will have a new menu entry which allows you to start the usage calculation of your methods. Once finished your View should be opened / updated and the methods displayed which are not called. By double-clicking on them you can jump to the related method.

Note: project.isOpen() returns false, if you select an open project only with the right mouse click. If you click the project with the left mouse you should be fine.

Of course you can easily think of possible extensions to this approach:

  1. Calculate the usage of all methods and show then in the table
  2. Make is work for several projects
  3. Display in the table if a method has private, protected, default or public access
  4. Introduce filter
  5. Create marker in the editor for the identified methods. See Eclipse Plugin Development – Resource Markers

And here is the project for download.

de.vogella.jdt.codeanalysis.source_1.0.0.200907151800

Last but not least if would be nice if the Eclipse API Tools could help us with finding “dead” code. If have therefore opened Bug 283574.

 

Eclipse Activities – Hide / Display certain UI elements

Monday, July 13th, 2009

Eclipse Activities can be used to hide / display certain UI elements, for example based on the role of the user. The related extension point is “org.eclipse.ui.activities”. A common use case for using activities is authorization, e.g. certain user get to see only a certain part of the ui which fits to their role.

Via activities you can for example restrict:

  • Editors
  • Views
  • Menus
  • Wizards

You define first the activities and then one or several activityPatternBindings. The activityPatternBindings defines which UI elements belong to an activities. These elements will only be displayed if the activity is set to true.

The Eclipse workbench will automatically persists activated activities.

The following will demonstate how activities can be used.

Create a new Eclipse RCP project “de.vogella.rcp.activities” based on the “Hello RCP” template.

Add a view “de.vogella.rcp.activities.view” to your RCP application and add this view via “org.eclipse.ui.perspectiveExtensions” to your application. The view should have the id “de.vogella.rcp.activities.view”. This view will later be filtered by the activity.

See Eclipse RCP Tutorial for how to create a view and add it to the perspective.

Add now two commands “de.vogella.rcp.activities.activate” and “de.vogella.rcp.activities.deactivate” with the following default handler to your application. Comands are in detail described in Eclipse Commands.


package de.vogella.rcp.activities.handler;

import java.util.HashSet;
import java.util.Set;

import org.eclipse.core.commands.AbstractHandler;
import org.eclipse.core.commands.ExecutionEvent;
import org.eclipse.core.commands.ExecutionException;
import org.eclipse.ui.activities.IActivityManager;
import org.eclipse.ui.activities.IWorkbenchActivitySupport;
import org.eclipse.ui.handlers.HandlerUtil;

public class Activate extends AbstractHandler {

@Override
public Object execute(ExecutionEvent event) throws ExecutionException {
IWorkbenchActivitySupport activitySupport = HandlerUtil
.getActiveWorkbenchWindow(event).getWorkbench()
.getActivitySupport();
IActivityManager activityManager = activitySupport.getActivityManager();
Set enabledActivities = new HashSet();
String id = "de.vogella.rcp.activities.view";
if (activityManager.getActivity(id).isDefined()) {
enabledActivities.add(id);

}
activitySupport.setEnabledActivityIds(enabledActivities);
return null;
}

}

package de.vogella.rcp.activities.handler;

import java.util.HashSet;
import java.util.Set;

import org.eclipse.core.commands.AbstractHandler;
import org.eclipse.core.commands.ExecutionEvent;
import org.eclipse.core.commands.ExecutionException;
import org.eclipse.ui.activities.IWorkbenchActivitySupport;
import org.eclipse.ui.handlers.HandlerUtil;

public class DeActivate extends AbstractHandler {

@Override
public Object execute(ExecutionEvent event) throws ExecutionException {
IWorkbenchActivitySupport activitySupport = HandlerUtil
.getActiveWorkbenchWindow(event).getWorkbench()
.getActivitySupport();
Set<String> enabledActivities = new HashSet<String>();
activitySupport.setEnabledActivityIds(enabledActivities);
// Now I have to reset the perspective to update also the views
HandlerUtil.getActiveWorkbenchWindow(event).getActivePage().resetPerspective();
return null;
}
}

Add both commands to the menu.

Add also the standard command “org.eclipse.ui.views.showView” to your menu. This command will later also be filtered by the activity.

Now add the extension point “org.eclipse.ui.activities”. Add an activity with the id “de.vogella.rcp.activities.view”. As you see above this activity will be activated / deactivated by the commands from above.

Add then two activityPatternBinding to your extension point. To assign a ui element to an activity you can use patterns ( isEqualityPattern=false) or Strings ( isEqualityPattern=true). The first part of the pattern / string is the plug-in id and the second part is the UI element.

For example the correct String for the view is: “de.vogella.rcp.activities/de.vogella.rcp.activities.view”.

After doing this your extension point should look like this in plugin.xml:

   <extension
         point="org.eclipse.ui.activities">
      <activity
            id="de.vogella.rcp.activities.view"
            name="Activate View">
      </activity>
      <activityPatternBinding
            activityId="de.vogella.rcp.activities.view"
            isEqualityPattern="false"
            pattern="de.vogella.rcp.activities/de.vogella.rcp.activities.view">
      </activityPatternBinding>
      <activityPatternBinding
            activityId="de.vogella.rcp.activities.view"
            isEqualityPattern="true"
            pattern="de.vogella.rcp.activities/org.eclipse.ui.views.showView">
      </activityPatternBinding>
   </extension>

If you run your application the view and menu item should not be there. If you press your command “Activate” then the view and the menu should get displayed. If you stop and re-start the application the activity should be still active as it gets persisted.

If you press your command “Deactivate” then the menu and the view is removed.

Activities can also be used together with core expressions and your own define expressions (which you define via ISourceProvider). See Eclipse Commands – Own expressions on how to define them.

Expression based activities are even better for defining authorization in your application as they will ignore API calls to them.

The full project can be downloaded here:

de.vogella.rcp.activities

 

Eclipse Papercut #2 – Changing Eclipse subversive default behavior

Wednesday, July 8th, 2009

Update: Igor Burilo (Eclipse Subversive developer) was kind enough to accept a modified version of patch developed in this episode. While I’m very happy about this, this unfortunately renders this example irrelevant . You still might want to read this to see how to checkout code from the Eclipse version control system.

In this second part of my 7 Paper Cuts in Eclipse I want to change some standard Eclipse code.

This second papercut is a bit more complex as the first papercut; I’m planning to return to simpler examples in the next papercut.

So lets define the papercut:

I frequently create example projects which I share in a svn repository. For related projects I use the mulitproject layout. The standard in subversion is “Simple Mode – One project for one repository”. Therefore everytime I share a project I have to switch in the subversion dialog from “Simple Mode” to “Advanced Mode”.

papercut1_10

Nothing big? I agree, hence the perfect example for a papercut.

To change the coding of this dialog we need to find the class which is responsible to display this page. Plug-in Spy make this easy via Alt + Shift + F1.

papercut1_20

Now you need to download the source code of the plug-in ”
org.eclipse.team.svn.ui”. This plug-in is stored in svn and can get access via subversion itself. The URL for the subversion repository view is: “http://dev.eclipse.org/svnroot/technology/org.eclipse.subversive”. See here to learn how to use svn and cvs to access the Eclipse code

Add this repository in the “SVN Repository” view and check-out (download) the plug-ins ”
org.eclipse.team.svn.ui” and “org.eclipse.team.svn.core”. You find them in “trunk”.

papercut1_30

Looking at the coding of SelectProjectNamePage.java changing the selection only requires a few lines to be changed.

I use Eclipse Preferences to remember the last user selection.

protected SelectProjectNamePageSimpleModeComposite simpleModeComposite;
	protected ShareProjectNameAdvancedModeComposite advancedModeComposite;
	private static String SELECTION_MODE = "isSimpleMode";

	public SelectProjectNamePage() {
		super(
			SelectProjectNamePage.class.getName(),
			"",  //$NON-NLS-1$
			SVNTeamUIPlugin.instance().getImageDescriptor("icons/wizards/newconnect.gif")); //$NON-NLS-1$
		Preferences preferences = new ConfigurationScope()
		.getNode("org.eclipse.team.svn.ui.wizard.shareproject");
		Preferences preference = preferences.node("note1");
		this.isSimpleMode = preference.getBoolean(SELECTION_MODE, true);;
	}

and I have to change a few more lines to use isSimpleMode everywhere:

this.simpleModeRadionButton.setSelection(this.isSimpleMode);
....
this.advancedModeRadionButton.setSelection(!this.isSimpleMode); // Either or
this.advancedModeRadionButton.addSelectionListener(modeListener);

And I have to set the preference in the ModeListener


	protected class ModeListener extends SelectionAdapter {
		public void widgetSelected(SelectionEvent e) {
			//change controls area mode
			Button modeButton = (Button) e.widget;
			if (SelectProjectNamePage.this.simpleModeRadionButton == modeButton && SelectProjectNamePage.this.isSimpleMode == false) {
				SelectProjectNamePage.this.isSimpleMode = true;
				preference.putBoolean(SELECTION_MODE, true);
				enableControlsArea();
			} else if (SelectProjectNamePage.this.advancedModeRadionButton == modeButton && SelectProjectNamePage.this.isSimpleMode == true) {
				SelectProjectNamePage.this.isSimpleMode = false;
				preference.putBoolean(SELECTION_MODE, false);
				enableControlsArea();
			}
		}
	}

The full source code is attached.

SelectProjectNamePage

If you now export these two plug-ins into your runnig Eclipse, this dialog will remember the last selection (Simple vrs. Advanced).

As I hope this behavior is useful to others I created a bug report (with a patch). See Bug . I hope the subversion programmers will consider this patch (or an improved version of it).

Remember sharing is good. Open Source does not only allow you to solve your issues but it makes it easy to contribute back.

See Eclipse Papercuts to get all posts in this series.

 

Eclipse Papercut #1 – The annoying creation of the project package

Monday, July 6th, 2009

This series is about simplifying the handling of Eclipse for simple but repetitive tasks. See 7 Paper Cuts in Eclipse.

Ok lets start with something simple.

By convension Eclipse projects are named based on reverse URL’s, e.g. “org.eclipse.jdt” or “de.vogella.test”. Also this convension suggests to create a package with the same name as the project. For this you have to select the src folder in the project, right click on it, select New – Package then copy the name of the project from the first line and paste it into the second line of the dialog.

papercut1_10

While you may argue that this is not to bad as the amount of project you are creating is limited; I personally find this annoying as I’m writting lots of example for my articles on http://www.vogella.de/.

Time to simplify (at least a little bit).

Create an Eclipse plug-in called “de.vogella.jdt.packageexplorer”. Add a Eclipse command to it with the following default handler.


package de.vogella.jdt.packageexplorer.handler;

import org.eclipse.core.commands.AbstractHandler;
import org.eclipse.core.commands.ExecutionEvent;
import org.eclipse.core.commands.ExecutionException;
import org.eclipse.core.resources.IFolder;
import org.eclipse.jdt.core.IJavaProject;
import org.eclipse.jdt.core.IPackageFragmentRoot;
import org.eclipse.jdt.core.JavaModelException;
import org.eclipse.jface.viewers.IStructuredSelection;
import org.eclipse.ui.handlers.HandlerUtil;

public class AddPackage extends AbstractHandler {

	@Override
	public Object execute(ExecutionEvent event) throws ExecutionException {

		IStructuredSelection selection = (IStructuredSelection) HandlerUtil
				.getActiveMenuSelection(event);
		Object firstElement = selection.getFirstElement();
		if (firstElement instanceof IJavaProject) {
			IJavaProject javaProject = (IJavaProject) firstElement;
			try {
				IFolder folder = javaProject.getProject().getFolder("src");
				// folder.create(true, true, null);
				IPackageFragmentRoot srcFolder = javaProject
						.getPackageFragmentRoot(folder);
				srcFolder.createPackageFragment(javaProject.getProject()
						.getName(), true, null);
			} catch (JavaModelException e) {
				e.printStackTrace();
			}
		}
		return null;
	}
}

Add this command to the content menu in the package explorer as described in Eclipse plug-in Development.

The resulting plugin.xml looks like the following:

<?xml version="1.0" encoding="UTF-8"?>
<?eclipse version="3.4"?>
<plugin>
   <extension
         point="org.eclipse.ui.menus">
      <menuContribution
            locationURI="popup:org.eclipse.jdt.ui.PackageExplorer">
         <command
               commandId="de.vogella.jdt.packageexplorer.AddPackage"
               label="Add Default Package"
               style="push">
         </command>
      </menuContribution>
   </extension>
   <extension
         point="org.eclipse.ui.commands">
      <command
            defaultHandler="de.vogella.jdt.packageexplorer.handler.AddPackage"
            id="de.vogella.jdt.packageexplorer.AddPackage"
            name="Add Default Package">
      </command>
   </extension>

</plugin>

Export your plug-in and put it into the dropin folder in your Eclipse installation. Restart Eclipse.

If you now create a new project you can right click on the project and directly create your project / default package.

papercut1_30

If you want to improve this further you can assign a keybinding to your command and use a shortcut to create the package. Or if you have more artifacts to create you can use the handler to create .java files, more packages, etc.

Here is the source bundle to download:
de.vogella.jdt.packageexplorer.source_1.0.0

Here is the exported plugin (you need you rename zip to jar before putting this into the Eclipse/dropin folder)

de.vogella.jdt.packageexplorer_1.0.0

See Eclipse Papercuts to get all posts in this series.

 

The Servlet within – Servlet creates Javascript coding

Saturday, July 4th, 2009

The following explains how to create Javascript output with a servlet and how to include this Javascript into a static HTML page. The generated Javascript code will again write some text into the HTML pages.

For this tutorial you need to use Eclipse and know how to develop servlets with Eclipse.

See Servlet and JSP development with Eclipse
in case you need an introduction.

Create a new dynamic Web project “de.vogella.wtp.javascript”. Create a package “de.vogella.wtp.javascript”.

Create a new servlet “JavaScriptServlet” in your package. The URL mapping should be done to /JSCounter.js.

jscounter10


package de.vogella.wtp.javascript;

import java.io.IOException;
import java.io.PrintWriter;
import java.util.concurrent.atomic.AtomicLong;

import javax.servlet.ServletException;
import javax.servlet.http.HttpServlet;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import javax.servlet.http.HttpSession;

/**
 * Servlet implementation class JavaScriptServlet
 */
public class JavaScriptServlet extends HttpServlet {
	private static final long serialVersionUID = 1L;
    private AtomicLong counter = new AtomicLong(0);   // We start with zero

	/**
	 * @see HttpServlet#doGet(HttpServletRequest request, HttpServletResponse response)
	 */
	protected void doGet(HttpServletRequest request, HttpServletResponse response) throws ServletException, IOException {
		HttpSession session = request.getSession(true);
		// Set the session valid for 2 secs
		session.setMaxInactiveInterval(1);
		response.setContentType("text/plain");
		PrintWriter out = response.getWriter();

		if (session.isNew()) {
			counter.incrementAndGet();
		}
		String s = "br.writeln(\"This site has been accessed "+ "" + counter.get() +"
"+"\");"; out.println("var br=document;"); out.println(s); } }

Run the servlet, as a result the system should give you a popup with a file to save.

Create now a static HTML page “index.html” under WebContent which calls the servlet. Example can be download from here.

Right click to download this file

This HTML page will call the servlet. The HTML page will then receive the response of the servlet and interprets the response as JavaScript. Open this HTML page in a browser while your servlet is running to display the counter.