/******************************************************************************* * Copyright 2005-2006, CHISEL Group, University of Victoria, Victoria, BC, Canada * and IBM Corporation. All rights reserved. This program and the accompanying materials * are made available under the terms of the Eclipse Public License v1.0 * which accompanies this distribution, and is available at * http://www.eclipse.org/legal/epl-v10.html * * Contributors: * The Chisel Group, University of Victoria *******************************************************************************/ package net.sourceforge.tagsea; import java.text.DateFormat; import java.text.ParseException; import java.util.Date; import java.util.Locale; import java.util.Map; import java.util.TreeMap; import java.util.TreeSet; import net.sourceforge.tagsea.core.ITagChangeListener; import net.sourceforge.tagsea.core.IWaypoint; import net.sourceforge.tagsea.core.IWaypointChangeEvent; import net.sourceforge.tagsea.core.IWaypointChangeListener; import net.sourceforge.tagsea.core.IWaypointLocator; import net.sourceforge.tagsea.core.StrictWaypointLocator; import net.sourceforge.tagsea.core.TagDelta; import net.sourceforge.tagsea.core.TagSEAChangeStatus; import net.sourceforge.tagsea.core.WaypointDelta; import net.sourceforge.tagsea.model.internal.Waypoint; import org.eclipse.core.runtime.IConfigurationElement; import org.eclipse.core.runtime.Status; /** * Class to be extended by implementors of a waypoint plugin. This delegate will handle * navigation to a waypoint in the workbench as well as answering questions about waypoints * such as whether or not the current state of the plugin will allow a waypoint to be * deleted by a UI element. * @author Del Myers */ public abstract class AbstractWaypointDelegate implements IWaypointType { //for use in configuration private static final String TYPE_ATTRIBUTE ="type"; //$NON-NLS-1$ private static final String NAME_ATTRIBUTE = "name"; //$NON-NLS-1$ private static final String ATTRIBUTE_ATTRIBUTE = "attribute"; //$NON-NLS-1$ private static final String INT_TYPE = "integer"; //$NON-NLS-1$ private static final String STRING_TYPE = "string"; //$NON-NLS-1$ private static final String BOOL_TYPE = "boolean"; //$NON-NLS-1$ private static final String DATE_TYPE = "date"; //$NON-NLS-1$ private static final String DEFAULT_ATTRIBUTE = "default"; //$NON-NLS-1$ private static final String PARENT_FIELD = "parent"; private String name; private String type; private TreeSet<String> parentList; private boolean parentsResolved; private Map<String, Object> attributeDefaultMap; private LocalWaypointChangeListener waypointListener; private LocalTagListener tagListener; private StrictWaypointLocator locator; private class LocalTagListener implements ITagChangeListener { public void tagsChanged(TagDelta delta) { AbstractWaypointDelegate.this.tagsChanged(delta); } } private class LocalWaypointChangeListener implements IWaypointChangeListener { public void waypointsChanged(WaypointDelta delta) { AbstractWaypointDelegate.this.waypointsChanged(delta); } } /** * */ protected AbstractWaypointDelegate() { parentsResolved = false; name = null; type = null; parentList = null; } /** * Asks the delegate to navigate to this waypoint in the workbench. This could include * tasks such as opening up a web browser, or navigating to a line in an editor. Implementors * should not expect this call to be made from the UI thread. * @param waypoint the waypoint to navigate to. */ protected abstract void navigate(IWaypoint waypoint); /** * Initializes the delegate and loads all the previously saved waypoints. * */ public final void initialize() { if (this.waypointListener == null) { this.waypointListener = new LocalWaypointChangeListener(); this.tagListener = new LocalTagListener(); } TagSEAPlugin.addTagChangeListener(this.tagListener); TagSEAPlugin.addWaypointChangeListener(this.waypointListener, getType()); load(); } /** * Loads all the previously saved waypoints and contributes them to the model. This happens * at start up only once for each delegate. Delegates will be notified of new or changed * waypoint/tag events that occur during this process. It is recommended that plugins register * themselves as save participants so that this process will have the most up-to-date * information possible. */ protected abstract void load(); /* (non-Javadoc) * @see net.sourceforge.tagsea.IWaypointType#getType() */ public String getType() { return this.type; } /* (non-Javadoc) * @see net.sourceforge.tagsea.IWaypointType#getName() */ public String getName() { return this.name; } /* (non-Javadoc) * @see net.sourceforge.tagsea.IWaypointType#getDeclaredAttributes() */ public final String[] getDeclaredAttributes() { resolveParents(); return attributeDefaultMap.keySet().toArray(new String[attributeDefaultMap.size()]); } /** * Resolves all the attributes for the parent types of this waypoint. */ private void resolveParents() { if (parentsResolved) return; //first make sure that all parent types are in the list of parents. String[] parents = parentList.toArray(new String[parentList.size()]); for (String parent : parents) { IWaypointType parentType = TagSEAPlugin.getDefault().getWaypointType(parent); if (getType().equals(parent)) continue; if (parentType instanceof AbstractWaypointDelegate) { ((AbstractWaypointDelegate)parentType).resolveParents(); parentList.addAll(((AbstractWaypointDelegate)parentType).parentList); } } //get all the attributes for the parents for (String parent : parentList) { IWaypointType parentType = TagSEAPlugin.getDefault().getWaypointType(parent); if (getType().equals(parent)) continue; if (parentType != null) { String[] parentAttrs = parentType.getDeclaredAttributes(); for (String attr : parentAttrs) { if (!attributeDefaultMap.containsKey(attr)) { attributeDefaultMap.put(attr, parentType.getDefaultValue(attr)); } } } } parentsResolved = true; } /* (non-Javadoc) * @see net.sourceforge.tagsea.IWaypointType#isDeclaredAttribute(java.lang.String) */ public final boolean isDeclaredAttribute(String attribute) { resolveParents(); return attributeDefaultMap.containsKey(attribute); } /* (non-Javadoc) * @see net.sourceforge.tagsea.IWaypointType#getAttributeType(java.lang.String) */ public final Class<?> getAttributeType(String attribute) { if (!isDeclaredAttribute(attribute)) return null; return attributeDefaultMap.get(attribute).getClass(); } /* (non-Javadoc) * @see net.sourceforge.tagsea.IWaypointType#getDefaultValue(java.lang.String) */ public final Object getDefaultValue(String attribute) { resolveParents(); return attributeDefaultMap.get(attribute); } /** * Configures this delegate according to the given configuration element. * @param e */ final boolean configure(IConfigurationElement e) { this.name = e.getAttribute(NAME_ATTRIBUTE); this.type = e.getContributor().getName() + "." + e.getAttribute(TYPE_ATTRIBUTE); attributeDefaultMap = new TreeMap<String, Object>(); //add the default attributes attributeDefaultMap.put(IWaypoint.ATTR_AUTHOR, ""); attributeDefaultMap.put(IWaypoint.ATTR_MESSAGE, ""); attributeDefaultMap.put(IWaypoint.ATTR_DATE, new Date(0)); TreeSet<String> parents = new TreeSet<String>(); parents.add(IWaypoint.BASE_WAYPOINT); IConfigurationElement[] children = e.getChildren(); for (IConfigurationElement child : children) { if (ATTRIBUTE_ATTRIBUTE.equals(child.getName())) { //configure the attributes. String name = child.getAttribute(NAME_ATTRIBUTE); String type = child.getAttribute(TYPE_ATTRIBUTE); String def = child.getAttribute(DEFAULT_ATTRIBUTE); //check the types if (INT_TYPE.equals(type)) { int value = 0; if (def != null) { try { value = Integer.parseInt(def); } catch (NumberFormatException ex) { Status status = new Status( Status.ERROR, TagSEAPlugin.PLUGIN_ID, Status.ERROR, "Bad value for attribute " + name, ex ); TagSEAPlugin.getDefault().getLog().log(status); } } attributeDefaultMap.put(name, new Integer(value)); } else if (STRING_TYPE.equals(type)) { String value = ""; if (def != null) value = def; attributeDefaultMap.put(name, value); } else if (BOOL_TYPE.equals(type)) { boolean value = false; if (def != null) { value = Boolean.parseBoolean(def); } attributeDefaultMap.put(name, new Boolean(value)); } else if (DATE_TYPE.equals(type)) { Date value = new Date(0); if (def != null) { int colon = def.indexOf(':'); if (colon == 4) { String localeString = def.substring(0, 2); String countryString = def.substring(2, 4); String dateString = def.substring(5); Locale locale = new Locale(localeString, countryString); DateFormat df = DateFormat.getDateInstance(DateFormat.SHORT, locale); try { value = df.parse(dateString); } catch (ParseException ex) { TagSEAPlugin.getDefault().log("Could not parse date " + dateString, ex); } } attributeDefaultMap.put(name, value); } } else { Status status = new Status( Status.ERROR, TagSEAPlugin.PLUGIN_ID, Status.ERROR, "Bad type value for attribute " + name, null ); TagSEAPlugin.getDefault().getLog().log(status); return false; } } else if (PARENT_FIELD.equals(child.getName())) { parents.add(child.getAttribute("type")); } } parentList = parents; return true; } /** * Local indication for when tags have been changed in the model. * @param delta the tag delta. */ protected abstract void tagsChanged(TagDelta delta); /** * Local indication for when waypoints of the type defined by this delegate have * been changed. * @param delta the waypoint delta. */ protected abstract void waypointsChanged(WaypointDelta delta); /** * A convenience method to check for equality between waypoints. This method does not * replace the equals() method on the waypoint itself, but may be used for UI functionality * that may need to find equality between waypoints for different criteria. For example: * to check for equality based on attribute values. The default functionality is to * check for identity of the waypoints in the workspace. Overriders can be assured * that the two waypoints are of the type defined by this delegate. * @param wp0 a waypoint. * @param wp1 a waypoint. * @return whether the waypoints are equal. */ protected boolean waypointsEqual(IWaypoint wp0, IWaypoint wp1) { return wp0.equals(wp1); } /** * * @param waypoint * @return */ public int waypointHashCode(IWaypoint waypoint) { return (int)((Waypoint)waypoint).getWorkbenchId(); } /** * Called by the platform before a change occurs on the waypoint for the given event to * allow the plugin to refuse to allow the platform to perform the change. By default, * TagSEAChangeStatus.SUCCESS_STATUS is returned for all changes. * @param event the event to process. * @return the status. */ public TagSEAChangeStatus processChange(IWaypointChangeEvent event) { return TagSEAChangeStatus.SUCCESS_STATUS; } /** * Checks to see if the given waypoint type is a parent of this type. * @param parentType the type to check. If this waypoint type is a subtype of the given type, * it can be guaranteed that this type has all of the attributes of the parent. * @return whether or not the given waypoint type is a parent of this type. */ public boolean isSubtypeOf(String parentType) { if (parentType == null) return false; for (String parent : parentList) { if (parent.equals(parentType)) return true; } return false; } /** * Returns the waypoint locator for this waypoint type. Clients may override to provide a different locator. * Returns an instance of {@link StrictWaypointLocator} by default. * @return the waypoint locator for this waypoint type. */ public IWaypointLocator getLocator() { if (this.locator == null) { this.locator = new StrictWaypointLocator(getType()); } return this.locator; } /* (non-Javadoc) * @see net.sourceforge.tagsea.IWaypointType#isDelegate() */ public final boolean isDelegate() { return true; } }