public final class Specification {

    private static final String MISSING = "Missing ";

    /**
     * Manifest Attribute Name object for SPECIFICATION_TITLE.
     */
    public static final Attributes.Name SPECIFICATION_TITLE
        = Attributes.Name.SPECIFICATION_TITLE;

    /**
     * Manifest Attribute Name object for SPECIFICATION_VERSION.
     */
    public static final Attributes.Name SPECIFICATION_VERSION
        = Attributes.Name.SPECIFICATION_VERSION;

    /**
     * Manifest Attribute Name object for SPECIFICATION_VENDOR.
     */
    public static final Attributes.Name SPECIFICATION_VENDOR
        = Attributes.Name.SPECIFICATION_VENDOR;

    /**
     * Manifest Attribute Name object for IMPLEMENTATION_TITLE.
     */
    public static final Attributes.Name IMPLEMENTATION_TITLE
        = Attributes.Name.IMPLEMENTATION_TITLE;

    /**
     * Manifest Attribute Name object for IMPLEMENTATION_VERSION.
     */
    public static final Attributes.Name IMPLEMENTATION_VERSION
        = Attributes.Name.IMPLEMENTATION_VERSION;

    /**
     * Manifest Attribute Name object for IMPLEMENTATION_VENDOR.
     */
    public static final Attributes.Name IMPLEMENTATION_VENDOR
        = Attributes.Name.IMPLEMENTATION_VENDOR;

    /**
     * Enum indicating that extension is compatible with other Package
     * Specification.
     */
    public static final Compatibility COMPATIBLE =
        new Compatibility("COMPATIBLE");

    /**
     * Enum indicating that extension requires an upgrade
     * of specification to be compatible with other Package Specification.
     */
    public static final Compatibility REQUIRE_SPECIFICATION_UPGRADE =
        new Compatibility("REQUIRE_SPECIFICATION_UPGRADE");

    /**
     * Enum indicating that extension requires a vendor
     * switch to be compatible with other Package Specification.
     */
    public static final Compatibility REQUIRE_VENDOR_SWITCH =
        new Compatibility("REQUIRE_VENDOR_SWITCH");

    /**
     * Enum indicating that extension requires an upgrade
     * of implementation to be compatible with other Package Specification.
     */
    public static final Compatibility REQUIRE_IMPLEMENTATION_CHANGE =
        new Compatibility("REQUIRE_IMPLEMENTATION_CHANGE");

    /**
     * This enum indicates that an extension is incompatible with
     * other Package Specification in ways other than other enums
     * indicate. For example, the other Package Specification
     * may have a different ID.
     */
    public static final Compatibility INCOMPATIBLE =
        new Compatibility("INCOMPATIBLE");

    /**
     * The name of the Package Specification.
     */
    private String specificationTitle;

    /**
     * The version number (dotted decimal notation) of the specification
     * to which this optional package conforms.
     */
    private DeweyDecimal specificationVersion;

    /**
     * The name of the company or organization that originated the
     * specification to which this specification conforms.
     */
    private String specificationVendor;

    /**
     * The title of implementation.
     */
    private String implementationTitle;

    /**
     * The name of the company or organization that produced this
     * implementation of this specification.
     */
    private String implementationVendor;

    /**
     * The version string for implementation. The version string is
     * opaque.
     */
    private String implementationVersion;

    /**
     * The sections of jar that the specification applies to.
     */
    private String[] sections;

    /**
     * Return an array of Package Specification objects.
     * If there are no such optional packages, a zero-length array is returned.
     *
     * @param manifest Manifest to be parsed
     * @return the Package Specifications extensions in specified manifest
     * @throws ParseException if the attributes of the specifications cannot
     * be parsed according to their expected formats.
     */
    public static Specification[] getSpecifications(final Manifest manifest)
        throws ParseException {
        if (null == manifest) {
            return new Specification[ 0 ];
        }

        final ArrayList results = new ArrayList();

        final Map entries = manifest.getEntries();
        final Iterator keys = entries.keySet().iterator();
        while (keys.hasNext()) {
            final String key = (String) keys.next();
            final Attributes attributes = (Attributes) entries.get(key);
            final Specification specification
                = getSpecification(key, attributes);
            if (null != specification) {
                results.add(specification);
            }
        }

        final ArrayList trimmedResults = removeDuplicates(results);
        return (Specification[]) trimmedResults.toArray(new Specification[trimmedResults.size()]);
    }

    /**
     * The constructor to create Package Specification object.
     * Note that every component is allowed to be specified
     * but only the specificationTitle is mandatory.
     *
     * @param specificationTitle the name of specification.
     * @param specificationVersion the specification Version.
     * @param specificationVendor the specification Vendor.
     * @param implementationTitle the title of implementation.
     * @param implementationVersion the implementation Version.
     * @param implementationVendor the implementation Vendor.
     */
    public Specification(final String specificationTitle,
                          final String specificationVersion,
                          final String specificationVendor,
                          final String implementationTitle,
                          final String implementationVersion,
                          final String implementationVendor) {
        this(specificationTitle, specificationVersion, specificationVendor,
              implementationTitle, implementationVersion, implementationVendor,
              null);
    }

    /**
     * The constructor to create Package Specification object.
     * Note that every component is allowed to be specified
     * but only the specificationTitle is mandatory.
     *
     * @param specificationTitle the name of specification.
     * @param specificationVersion the specification Version.
     * @param specificationVendor the specification Vendor.
     * @param implementationTitle the title of implementation.
     * @param implementationVersion the implementation Version.
     * @param implementationVendor the implementation Vendor.
     * @param sections the sections/packages that Specification applies to.
     */
    public Specification(final String specificationTitle,
                          final String specificationVersion,
                          final String specificationVendor,
                          final String implementationTitle,
                          final String implementationVersion,
                          final String implementationVendor,
                          final String[] sections) {
        this.specificationTitle = specificationTitle;
        this.specificationVendor = specificationVendor;

        if (null != specificationVersion) {
            try {
                this.specificationVersion
                    = new DeweyDecimal(specificationVersion);
            } catch (final NumberFormatException nfe) {
                final String error = "Bad specification version format '"
                    + specificationVersion + "' in '" + specificationTitle
                    + "'. (Reason: " + nfe + ")";
                throw new IllegalArgumentException(error);
            }
        }

        this.implementationTitle = implementationTitle;
        this.implementationVendor = implementationVendor;
        this.implementationVersion = implementationVersion;

        if (null == this.specificationTitle) {
            throw new NullPointerException("specificationTitle");
        }

        String[] copy = null;
        if (null != sections) {
            copy = new String[ sections.length ];
            System.arraycopy(sections, 0, copy, 0, sections.length);
        }
        this.sections = copy;
    }

    /**
     * Get the title of the specification.
     *
     * @return the title of speciication
     */
    public String getSpecificationTitle() {
        return specificationTitle;
    }

    /**
     * Get the vendor of the specification.
     *
     * @return the vendor of the specification.
     */
    public String getSpecificationVendor() {
        return specificationVendor;
    }

    /**
     * Get the title of the specification.
     *
     * @return the title of the specification.
     */
    public String getImplementationTitle() {
        return implementationTitle;
    }

    /**
     * Get the version of the specification.
     *
     * @return the version of the specification.
     */
    public DeweyDecimal getSpecificationVersion() {
        return specificationVersion;
    }

    /**
     * Get the vendor of the extensions implementation.
     *
     * @return the vendor of the extensions implementation.
     */
    public String getImplementationVendor() {
        return implementationVendor;
    }

    /**
     * Get the version of the implementation.
     *
     * @return the version of the implementation.
     */
    public String getImplementationVersion() {
        return implementationVersion;
    }

    /**
     * Return an array containing sections to which specification applies
     * or null if relevent to no sections.
     *
     * @return an array containing sections to which specification applies
     *         or null if relevent to no sections.
     */
    public String[] getSections() {
        if (null == sections) {
            return null;
        }
        final String[] newSections = new String[ sections.length ];
        System.arraycopy(sections, 0, newSections, 0, sections.length);
        return newSections;
    }

    /**
     * Return a Compatibility enum indicating the relationship of this
     * Package Specification with the specified
     * Extension.
     *
     * @param other the other specification
     * @return the enum indicating the compatibility (or lack thereof)
     *         of specifed Package Specification
     */
    public Compatibility getCompatibilityWith(final Specification other) {
        if (!specificationTitle.equals(other.getSpecificationTitle())) {
            return INCOMPATIBLE;
        }

        final DeweyDecimal otherSpecificationVersion
            = other.getSpecificationVersion();
        if (null != specificationVersion) {
            if (null == otherSpecificationVersion
                || !isCompatible(specificationVersion, otherSpecificationVersion)) {
                return REQUIRE_SPECIFICATION_UPGRADE;
            }
        }

        final String otherImplementationVendor
            = other.getImplementationVendor();
        if (null != implementationVendor) {
            if (null == otherImplementationVendor
                || !implementationVendor.equals(otherImplementationVendor)) {
                return REQUIRE_VENDOR_SWITCH;
            }
        }

        final String otherImplementationVersion
            = other.getImplementationVersion();
        if (null != implementationVersion) {
            if (null == otherImplementationVersion
                || !implementationVersion.equals(otherImplementationVersion)) {
                return REQUIRE_IMPLEMENTATION_CHANGE;
            }
        }

        return COMPATIBLE;
    }

    /**
     * Return true if the specified package
     * is satisfied by this Specification. Otherwise, return
     * false.
     *
     * @param other the specification
     * @return true if the specification is compatible with this specification
     */
    public boolean isCompatibleWith(final Specification other) {
        return (COMPATIBLE == getCompatibilityWith(other));
    }

    /**
     * Return a String representation of this object.
     *
     * @return string representation of object.
     */
    public String toString() {
        final String brace = ": ";

        final StringBuffer sb
            = new StringBuffer(SPECIFICATION_TITLE.toString());
        sb.append(brace);
        sb.append(specificationTitle);
        sb.append(StringUtils.LINE_SEP);

        if (null != specificationVersion) {
            sb.append(SPECIFICATION_VERSION);
            sb.append(brace);
            sb.append(specificationVersion);
            sb.append(StringUtils.LINE_SEP);
        }

        if (null != specificationVendor) {
            sb.append(SPECIFICATION_VENDOR);
            sb.append(brace);
            sb.append(specificationVendor);
            sb.append(StringUtils.LINE_SEP);
        }

        if (null != implementationTitle) {
            sb.append(IMPLEMENTATION_TITLE);
            sb.append(brace);
            sb.append(implementationTitle);
            sb.append(StringUtils.LINE_SEP);
        }

        if (null != implementationVersion) {
            sb.append(IMPLEMENTATION_VERSION);
            sb.append(brace);
            sb.append(implementationVersion);
            sb.append(StringUtils.LINE_SEP);
        }

        if (null != implementationVendor) {
            sb.append(IMPLEMENTATION_VENDOR);
            sb.append(brace);
            sb.append(implementationVendor);
            sb.append(StringUtils.LINE_SEP);
        }

        return sb.toString();
    }

    /**
     * Return true if the first version number is greater than
     * or equal to the second; otherwise return false.
     *
     * @param first First version number (dotted decimal)
     * @param second Second version number (dotted decimal)
     */
    private boolean isCompatible(final DeweyDecimal first,
                                 final DeweyDecimal second) {
        return first.isGreaterThanOrEqual(second);
    }

    /**
     * Combine all specifications objects that are identical except
     * for the sections.
     *
     *
Note this is very inefficent and should probably be fixed
     * in the future.

     *
     * @param list the array of results to trim
     * @return an array list with all duplicates removed
     */
    private static ArrayList removeDuplicates(final ArrayList list) {
        final ArrayList results = new ArrayList();
        final ArrayList sections = new ArrayList();
        while (list.size() > 0) {
            final Specification specification = (Specification) list.remove(0);
            final Iterator iterator = list.iterator();
            while (iterator.hasNext()) {
                final Specification other = (Specification) iterator.next();
                if (isEqual(specification, other)) {
                    final String[] otherSections = other.getSections();
                    if (null != otherSections) {
                        sections.addAll(Arrays.asList(otherSections));
                    }
                    iterator.remove();
                }
            }

            final Specification merged =
                mergeInSections(specification, sections);
            results.add(merged);
            sections.clear();
        }

        return results;
    }

    /**
     * Test if two specifications are equal except for their sections.
     *
     * @param specification one specificaiton
     * @param other the ohter specification
     * @return true if two specifications are equal except for their
     *         sections, else false
     */
    private static boolean isEqual(final Specification specification,
                                    final Specification other) {
        return
            specification.getSpecificationTitle().equals(other.getSpecificationTitle())
            && specification.getSpecificationVersion().isEqual(other.getSpecificationVersion())
            && specification.getSpecificationVendor().equals(other.getSpecificationVendor())
            && specification.getImplementationTitle().equals(other.getImplementationTitle())
            && specification.getImplementationVersion().equals(other.getImplementationVersion())
            && specification.getImplementationVendor().equals(other.getImplementationVendor());
    }

    /**
     * Merge the specified sections into specified section and return result.
     * If no sections to be added then just return original specification.
     *
     * @param specification the specification
     * @param sectionsToAdd the list of sections to merge
     * @return the merged specification
     */
    private static Specification mergeInSections(final Specification specification,
                                              final ArrayList sectionsToAdd) {
        if (0 == sectionsToAdd.size()) {
            return specification;
        }
        sectionsToAdd.addAll(Arrays.asList(specification.getSections()));

        final String[] sections =
            (String[]) sectionsToAdd.toArray(new String[sectionsToAdd.size()]);

        return new Specification(specification.getSpecificationTitle(),
                specification.getSpecificationVersion().toString(),
                specification.getSpecificationVendor(),
                specification.getImplementationTitle(),
                specification.getImplementationVersion(),
                specification.getImplementationVendor(),
                sections);
    }

    /**
     * Trim the supplied string if the string is non-null
     *
     * @param value the string to trim or null
     * @return the trimmed string or null
     */
    private static String getTrimmedString(final String value) {
        return value == null ? null : value.trim();
    }

    /**
     * Extract an Package Specification from Attributes.
     *
     * @param attributes Attributes to searched
     * @return the new Specification object, or null
     */
    private static Specification getSpecification(final String section,
                                                   final Attributes attributes)
        throws ParseException {
        final String name
            = getTrimmedString(attributes.getValue(SPECIFICATION_TITLE));
        if (null == name) {
            return null;
        }

        final String specVendor
            = getTrimmedString(attributes.getValue(SPECIFICATION_VENDOR));
        if (null == specVendor) {
            throw new ParseException(MISSING + SPECIFICATION_VENDOR, 0);
        }

        final String specVersion
            = getTrimmedString(attributes.getValue(SPECIFICATION_VERSION));
        if (null == specVersion) {
            throw new ParseException(MISSING + SPECIFICATION_VERSION, 0);
        }

        final String impTitle
            = getTrimmedString(attributes.getValue(IMPLEMENTATION_TITLE));
        if (null == impTitle) {
            throw new ParseException(MISSING + IMPLEMENTATION_TITLE, 0);
        }

        final String impVersion
            = getTrimmedString(attributes.getValue(IMPLEMENTATION_VERSION));
        if (null == impVersion) {
            throw new ParseException(MISSING + IMPLEMENTATION_VERSION, 0);
        }

        final String impVendor
            = getTrimmedString(attributes.getValue(IMPLEMENTATION_VENDOR));
        if (null == impVendor) {
            throw new ParseException(MISSING + IMPLEMENTATION_VENDOR, 0);
        }

        return new Specification(name, specVersion, specVendor,
                                  impTitle, impVersion, impVendor,
                                  new String[]{section});

FUNC_DECL RETURN IF IF IF IF IF VAR_DECL VAR_DECL CALL BINARY_OP BINARY_OP BINARY_OP BINARY_OP BINARY_OP CALL CALL CALL LITERAL_ANCHOR LITERAL_ANCHOR LITERAL_ANCHOR LITERAL_ANCHOR LITERAL_ANCHOR CALL LITERAL_ANCHOR CALL CALL CALL CALL CALL CALL CALL CALL CALL CALL CALL CALL CALL CALL CALL CALL CALL CALL CALL CALL CALL

FUNC_DECL RETURN PARAM PARAM BINARY_OP CALL BINARY_OP CALL CALL BINARY_OP CALL CALL CALL BINARY_OP CALL CALL CALL BINARY_OP CALL CALL CALL CALL CALL CALL CALL CALL CALL

FUNC_DECL RETURN VAR_DECL IF PARAM PARAM CALL CALL BINARY_OP CALL CALL CALL CALL CALL CALL CALL RETURN CALL LITERAL_ANCHOR CALL CALL CALL CALL

FUNC_DECL PARAM PARAM PARAM PARAM PARAM PARAM LITERAL_ANCHOR


org.gjt.sp.jedit.browser.VFSBrowser

public class VFSBrowser extends JPanel implements EBComponent,
	DefaultFocusComponent, DockableWindow
{
	public static final String NAME = "vfs.browser";

	//{{{ Browser types
	/**
	 * Open file dialog mode. Equals JFileChooser.OPEN_DIALOG for
	 * backwards compatibility.
	 */
	public static final int OPEN_DIALOG = 0;

	/**
	 * Save file dialog mode. Equals JFileChooser.SAVE_DIALOG for
	 * backwards compatibility.
	 */
	public static final int SAVE_DIALOG = 1;
	/**
	 * Choose directory dialog mode.
	 */
	public static final int BROWSER_DIALOG = 4;
	/**
	 * Choose directory dialog mode.
	 */
	public static final int CHOOSE_DIRECTORY_DIALOG = 3;

	/**
	 * Stand-alone browser mode.
	 */
	public static final int BROWSER = 2;
	//}}}

	//{{{ browseDirectoryInNewWindow() method
	/**
	 * Opens the specified directory in a new, floating, file system browser.
	 * @param view The view
	 * @param path The directory's path
	 * @since jEdit 4.1pre2
	 */
	public static void browseDirectoryInNewWindow(View view, String path)
	{
		DockableWindowManager wm = view.getDockableWindowManager();
		if(path != null)
		{
			// this is such a bad way of doing it, but oh well...
			jEdit.setTemporaryProperty("vfs.browser.path.tmp",path);
		}
		wm.floatDockableWindow("vfs.browser");
		jEdit.unsetProperty("vfs.browser.path.tmp");
	} //}}}

	//{{{ browseDirectory() method
	/**
	 * Opens the specified directory in a file system browser.
	 * @param view The view
	 * @param path The directory's path
	 * @since jEdit 4.0pre3
	 */
	public static void browseDirectory(View view, String path)
	{
		DockableWindowManager wm = view.getDockableWindowManager();
		VFSBrowser browser = (VFSBrowser)wm.getDockable(NAME);
		if(browser != null)
		{
			wm.showDockableWindow(NAME);
			browser.setDirectory(path);
		}
		else
		{
			if(path != null)
			{
				// this is such a bad way of doing it, but oh well...
				jEdit.setTemporaryProperty("vfs.browser.path.tmp",path);
			}
			wm.addDockableWindow("vfs.browser");
			jEdit.unsetProperty("vfs.browser.path.tmp");
		}
	} //}}}

	//{{{ getActionContext() method
	/**
	 * Returns the browser action context.
	 * @since jEdit 4.2pre1
	 */
	public static ActionContext getActionContext()
	{
		return actionContext;
	} //}}}

	//{{{ VFSBrowser constructor
	/**
	 * Creates a new VFS browser.
	 * @param view The view to open buffers in by default
	 */
	public VFSBrowser(View view, String position)
	{
		this(view,null,BROWSER,true,position);
	} //}}}

	//{{{ VFSBrowser constructor
	/**
	 * Creates a new VFS browser.
	 * @param view The view to open buffers in by default
	 * @param path The path to display
	 * @param mode The browser mode
	 * @param multipleSelection True if multiple selection should be allowed
	 * @param position Where the browser is located
	 * @since jEdit 4.2pre1
	 */
	public VFSBrowser(View view, String path, int mode,
		boolean multipleSelection, String position)
	{
		super(new BorderLayout());

		listenerList = new EventListenerList();

		this.mode = mode;
		this.multipleSelection = multipleSelection;
		this.view = view;

		DockableWindowManager dwm = view.getDockableWindowManager();
		KeyListener keyListener = dwm.closeListener(NAME);
		addKeyListener(keyListener);

		currentEncoding = jEdit.getProperty("buffer.encoding",
			System.getProperty("file.encoding"));
		autoDetectEncoding = jEdit.getBooleanProperty(
			"buffer.encodingAutodetect");

		ActionHandler actionHandler = new ActionHandler();

		topBox = new Box(BoxLayout.Y_AXIS);
		horizontalLayout = (mode != BROWSER
			|| DockableWindowManager.TOP.equals(position)
			|| DockableWindowManager.BOTTOM.equals(position));

		toolbarBox = new Box(horizontalLayout
			? BoxLayout.X_AXIS
			: BoxLayout.Y_AXIS);

		topBox.add(toolbarBox);

		GridBagLayout layout = new GridBagLayout();
		pathAndFilterPanel = new JPanel(layout);
		if(isHorizontalLayout())
			pathAndFilterPanel.setBorder(new EmptyBorder(12,12,12,12));

		GridBagConstraints cons = new GridBagConstraints();
		cons.gridwidth = cons.gridheight = 1;
		cons.gridx = cons.gridy = 0;
		cons.fill = GridBagConstraints.BOTH;
		cons.anchor = GridBagConstraints.EAST;
		JLabel label = new JLabel(jEdit.getProperty("vfs.browser.path"),
			SwingConstants.RIGHT);
		label.setBorder(new EmptyBorder(0,0,0,12));
		layout.setConstraints(label,cons);
		pathAndFilterPanel.add(label);

		pathField = new HistoryTextField("vfs.browser.path");
		pathField.setName("path");
		pathField.addKeyListener(keyListener);
		pathField.setInstantPopups(true);
		pathField.setEnterAddsToHistory(false);
		pathField.setSelectAllOnFocus(true);

		if (mode == BROWSER)
		{
			pathField.addKeyListener(new KeyAdapter()
			{
				public void keyReleased(KeyEvent e)
				{
					if (e.getKeyCode() == KeyEvent.VK_ESCAPE)
					{
						pathField.setText(VFSBrowser.this.path);
					}
				}
			});
		}

		// because its preferred size can be quite wide, we
		// don't want it to make the browser way too big,
		// so set the preferred width to 0.
		Dimension prefSize = pathField.getPreferredSize();
		prefSize.width = 0;
		pathField.setPreferredSize(prefSize);
		pathField.addActionListener(actionHandler);
		cons.gridx = 1;
		cons.weightx = 1.0;
		cons.gridwidth = GridBagConstraints.REMAINDER;

		layout.setConstraints(pathField,cons);
		pathAndFilterPanel.add(pathField);

		filterCheckbox = new JCheckBox(jEdit.getProperty("vfs.browser.filter"));
		filterCheckbox.setMargin(new Insets(0,0,0,0));
//		filterCheckbox.setRequestFocusEnabled(false);
		filterCheckbox.setBorder(new EmptyBorder(0,0,0,12));
		filterCheckbox.setSelected(jEdit.getBooleanProperty(
			"vfs.browser.filter-enabled"));

		filterCheckbox.addActionListener(actionHandler);
		filterCheckbox.addKeyListener(keyListener);
		filterCheckbox.setName("filter-checkbox");
		if(mode != CHOOSE_DIRECTORY_DIALOG)
		{
			cons.gridwidth = 1;
			cons.gridx = 0;
			cons.weightx = 0.0;
			cons.gridy = 1;
			layout.setConstraints(filterCheckbox,cons);
			pathAndFilterPanel.add(filterCheckbox);
		}

		filterField = new JComboBox();
		filterEditor = new HistoryComboBoxEditor("vfs.browser.filter");
		filterEditor.setToolTipText(jEdit.getProperty("glob.tooltip"));
		filterEditor.setInstantPopups(true);
		filterEditor.setSelectAllOnFocus(true);
		filterEditor.addActionListener(actionHandler);
		filterEditor.addKeyListener(keyListener);
		filterField.setName("filter-field");
		String filter;
		if(mode == BROWSER || !jEdit.getBooleanProperty(
			"vfs.browser.currentBufferFilter"))
		{
			filter = jEdit.getProperty("vfs.browser.last-filter");
			if(filter == null)
				filter = jEdit.getProperty("vfs.browser.default-filter");
		}
		else
		{
			String ext = MiscUtilities.getFileExtension(
				view.getBuffer().getName());
			if(ext.length() == 0)
				filter = jEdit.getProperty("vfs.browser.default-filter");
			else
				filter = '*' + ext;
		}

		// filterField.getEditor().setItem(new GlobVFSFileFilter(filter));
		// filterField.addItem(filterField.getEditor().getItem());
		filterEditor.setItem(new GlobVFSFileFilter(filter));
		filterField.addItem(filterEditor.getItem());
		filterField.addItemListener(actionHandler);
		filterField.setRenderer(new VFSFileFilterRenderer());

		// loads the registered VFSFileFilter services.
		String[] _filters = ServiceManager.getServiceNames(VFSFileFilter.SERVICE_NAME);
		for (int i = 0; i < _filters.length; i++)
		{
			VFSFileFilter _filter = (VFSFileFilter)
				ServiceManager.getService(VFSFileFilter.SERVICE_NAME, _filters[i]);
			filterField.addItem(_filter);
		}

		if(mode != CHOOSE_DIRECTORY_DIALOG)
		{
			cons.gridwidth = GridBagConstraints.REMAINDER;
			cons.fill = GridBagConstraints.HORIZONTAL;
			cons.gridx = 1;
			cons.weightx = 1.0;
			if (filterField.getItemCount() > 1)
			{
				filterField.setEditor(filterEditor);
				filterField.setEditable(true);
				layout.setConstraints(filterField,cons);
				pathAndFilterPanel.add(filterField);
			}
			else
			{
				layout.setConstraints(filterEditor,cons);
				pathAndFilterPanel.add(filterEditor);
			}
		}

		topBox.add(pathAndFilterPanel);
		add(BorderLayout.NORTH,topBox);

		add(BorderLayout.CENTER,browserView = new BrowserView(this));
		if(isHorizontalLayout())
			browserView.setBorder(new EmptyBorder(0,12,0,12));
		defaultFocusComponent = browserView.getTable();
		propertiesChanged();

		updateFilterEnabled();

		setFocusTraversalPolicy(new LayoutFocusTraversalPolicy());
		// see VFSBrowser.browseDirectory()
		if(path == null)
			path = jEdit.getProperty("vfs.browser.path.tmp");

		if(path == null || path.length() == 0)
		{
			String userHome = System.getProperty("user.home");
			String defaultPath = jEdit.getProperty("vfs.browser.defaultPath");
			if("home".equals(defaultPath))
				path = userHome;
			else if("working".equals(defaultPath))
				path = System.getProperty("user.dir");
			else if("buffer".equals(defaultPath))
			{
				Buffer buffer = view.getBuffer();
				path = buffer.getDirectory();
			}
			else if("last".equals(defaultPath))
			{
				HistoryModel pathModel = HistoryModel.getModel("vfs.browser.path");
				if(pathModel.getSize() == 0)
					path = "~";
				else
					path = pathModel.getItem(0);
			}
			else if("favorites".equals(defaultPath))
				path = "favorites:";
			else
			{
				// unknown value??!!!
				path = userHome;
			}
		}

		final String _path = path;

		SwingUtilities.invokeLater(new Runnable()
		{
			public void run()
			{
				setDirectory(_path);
			}
		});
	} //}}}

	//{{{ focusOnDefaultComponent() method
	public void focusOnDefaultComponent()
	{
		// pathField.requestFocus();
		defaultFocusComponent.requestFocus();
	} //}}}

	// {{{ setDefaultFocusComponent()
	/** Only used by VFSFileChooserDialog, since it embeds this in a dialog
	 */
	void setDefaultFocusComponent(JComponent c)
	{
		defaultFocusComponent = c;
	}// }}}

	//{{{ addNotify() method
	@Override
	public void addNotify()
	{
		super.addNotify();
		EditBus.addToBus(this);
	} //}}}

	//{{{ removeNotify() method
	@Override
	public void removeNotify()
	{
		super.removeNotify();
		jEdit.setBooleanProperty("vfs.browser.filter-enabled",
			filterCheckbox.isSelected());
		if(mode == BROWSER || !jEdit.getBooleanProperty(
			"vfs.browser.currentBufferFilter"))
		{
			VFSFileFilter selectedFilter =
				(VFSFileFilter) filterField.getSelectedItem();
			if (selectedFilter instanceof GlobVFSFileFilter)
				jEdit.setProperty("vfs.browser.last-filter",
					((GlobVFSFileFilter)selectedFilter).getGlob());
		}
		EditBus.removeFromBus(this);
	} //}}}

	//{{{ handleMessage() method
	public void handleMessage(EBMessage msg)
	{
		if(msg instanceof PropertiesChanged)
			propertiesChanged();
		else if(msg instanceof BufferUpdate)
		{
			BufferUpdate bmsg = (BufferUpdate)msg;
			if(bmsg.getWhat() == BufferUpdate.CREATED
				|| bmsg.getWhat() == BufferUpdate.CLOSED)
				browserView.updateFileView();
		}
		else if(msg instanceof PluginUpdate)
		{
			PluginUpdate pmsg = (PluginUpdate)msg;
			if((pmsg.getWhat() == PluginUpdate.LOADED ||
			   pmsg.getWhat() == PluginUpdate.UNLOADED) &&
				plugins != null /* plugins can be null if the VFSBrowser menu bar is hidden */)
			{
				plugins.updatePopupMenu();
			}
		}
		else if(msg instanceof VFSUpdate)
		{
			maybeReloadDirectory(((VFSUpdate)msg).getPath());
		}
	} //}}}

	//{{{ getView() method
	public View getView()
	{
		return view;
	} //}}}

	//{{{ getMode() method
	public int getMode()
	{
		return mode;
	} //}}}

	//{{{ isMultipleSelectionEnabled() method
	public boolean isMultipleSelectionEnabled()
	{
		return multipleSelection;
	} //}}}

	//{{{ isHorizontalLayout() method
	public boolean isHorizontalLayout()
	{
		return horizontalLayout;
	} //}}}

	//{{{ getShowHiddenFiles() method
	public boolean getShowHiddenFiles()
	{
		return showHiddenFiles;
	} //}}}

	//{{{ setShowHiddenFiles() method
	public void setShowHiddenFiles(boolean showHiddenFiles)
	{
		this.showHiddenFiles = showHiddenFiles;
	} //}}}

	//{{{ getFilenameFilter() method
	/**
	 * Returns the file name filter glob.
	 * @since jEdit 3.2pre2
	 * @deprecated Use {@link #getVFSFileFilter()} instead. This method
	 *             might return wrong information since jEdit 4.3pre6.
	 */
	@Deprecated
	public String getFilenameFilter()
	{
		if(filterCheckbox.isSelected())
		{
			String filter = filterField.getSelectedItem().toString();
			if(filter.length() == 0)
				return "*";
			else
				return filter;
		}
		else
			return "*";
	} //}}}

	//{{{ getVFSFileFilter() method
	/**
	 * Returns the currently active VFSFileFilter.
	 *
	 * @since jEdit 4.3pre7
	 */
	public VFSFileFilter getVFSFileFilter()
	{
		if (mode == CHOOSE_DIRECTORY_DIALOG)
			return new DirectoriesOnlyFilter();
		return 	(VFSFileFilter) filterField.getSelectedItem();
	} //}}}

	//{{{ addVFSFileFilter() method
	/**
	 * Adds a file filter to the browser.
	 *
	 * @since jEdit 4.3pre7
	 */
	public void addVFSFileFilter(VFSFileFilter filter)
	{
		filterField.addItem(filter);
		if (filterField.getItemCount() == 2)
		{
			filterField.setEditor(filterEditor);
			filterField.setEditable(true);

			GridBagLayout layout = (GridBagLayout) pathAndFilterPanel.getLayout();
			GridBagConstraints cons =layout.getConstraints(filterEditor);
			cons.gridwidth = GridBagConstraints.REMAINDER;
			cons.fill = GridBagConstraints.HORIZONTAL;
			cons.gridx = 1;
			cons.weightx = 1;

			pathAndFilterPanel.remove(filterEditor);
			layout.setConstraints(filterField, cons);
			pathAndFilterPanel.add(filterField);
			pathAndFilterPanel.validate();
			pathAndFilterPanel.repaint();
		}
	} //}}}

	//{{{ setFilenameFilter() method
	public void setFilenameFilter(String filter)
	{
		if(filter == null || filter.length() == 0 || "*".equals(filter))
			filterCheckbox.setSelected(false);
		else
		{
			filterCheckbox.setSelected(true);
			filterEditor.setItem(new GlobVFSFileFilter(filter));
		}
	} //}}}

	//{{{ getDirectoryField() method
	public HistoryTextField getDirectoryField()
	{
		return pathField;
	} //}}}

	//{{{ getDirectory() method
	public String getDirectory()
	{
		return path;
	} //}}}

	// {{{ Directory Stack operations
	/**
	 * @since jedit 4.3pre15
	 */
	public void previousDirectory()
	{
		if (historyStack.size() > 1)
		{
			historyStack.pop();
			nextDirectoryStack.push(path);
			setDirectory(historyStack.peek());
			historyStack.pop();
		}
	}

	/**
	 * @since jEdit 4.3pre15
	 */
	public void nextDirectory()
	{
		if (!nextDirectoryStack.isEmpty())
		{
			setDirectory(nextDirectoryStack.pop());
		}
	}
	// }}}

	//{{{ setDirectory() method
	public void setDirectory(String path)
	{
		if(path.startsWith("file:"))
			path = path.substring(5);
		path = MiscUtilities.expandVariables(path);
		pathField.setText(path);

		if(!startRequest())
			return;

		historyStack.push(path);
		browserView.saveExpansionState();
		browserView.loadDirectory(null,path,true);
		this.path = path;

		VFSManager.runInAWTThread(new Runnable()
		{
			public void run()
			{
				endRequest();
			}
		});
	} //}}}

	//{{{ getRootDirectory() method
	public static String getRootDirectory()
	{
		if(OperatingSystem.isMacOS() || OperatingSystem.isDOSDerived())
			return FileRootsVFS.PROTOCOL + ':';
		else
			return "/";
	} //}}}

	//{{{ rootDirectory() method
	/**
	 * Goes to the local drives directory.
	 * @since jEdit 4.0pre4
	 */
	public void rootDirectory()
	{
		setDirectory(getRootDirectory());
	} //}}}

	//{{{ reloadDirectory() method
	public void reloadDirectory()
	{
		// used by FTP plugin to clear directory cache
		VFSManager.getVFSForPath(path).reloadDirectory(path);

		browserView.saveExpansionState();
		browserView.loadDirectory(null,path,false);
	} //}}}

	//{{{ delete() method
	/**
	 * Note that all files must be on the same VFS.
	 * @since jEdit 4.3pre2
	 */
	public void delete(VFSFile[] files)
	{
		String dialogType;

		if(MiscUtilities.isURL(files[0].getDeletePath())
			&& FavoritesVFS.PROTOCOL.equals(
			MiscUtilities.getProtocolOfURL(files[0].getDeletePath())))
		{
			dialogType = "vfs.browser.delete-favorites";
		}
		else
		{
			dialogType = "vfs.browser.delete-confirm";
		}

		StringBuilder buf = new StringBuilder();
		String typeStr = "files";
		for(int i = 0; i < files.length; i++)
		{
			buf.append(files[i].getPath());
			buf.append('\n');
			if (files[i].getType() == VFSFile.DIRECTORY)
				typeStr = "directories and their contents";
		}

		Object[] args = { buf.toString(), typeStr};

		int result = GUIUtilities.confirm(this,dialogType,args,
			JOptionPane.YES_NO_OPTION,
			JOptionPane.WARNING_MESSAGE);
		if(result != JOptionPane.YES_OPTION)
			return;

		VFS vfs = VFSManager.getVFSForPath(files[0].getDeletePath());

		if(!startRequest())
			return;

		for(int i = 0; i < files.length; i++)
		{
			Object session = vfs.createVFSSession(files[i].getDeletePath(),this);
			if(session == null)
				continue;

			VFSManager.runInWorkThread(new BrowserIORequest(
				BrowserIORequest.DELETE,this,
				session,vfs,files[i].getDeletePath(),
				null,null));
		}

		VFSManager.runInAWTThread(new Runnable()
		{
			public void run()
			{
				endRequest();
			}
		});
	} //}}}

	//{{{ rename() method
	public void rename(String from)
	{
		VFS vfs = VFSManager.getVFSForPath(from);

		String filename = vfs.getFileName(from);
		String[] args = { filename };
		String to = GUIUtilities.input(this,"vfs.browser.rename",
			args,filename);
		if(to == null)
			return;

		to = MiscUtilities.constructPath(vfs.getParentOfPath(from),to);

		Object session = vfs.createVFSSession(from,this);
		if(session == null)
			return;

		if(!startRequest())
			return;

		VFSManager.runInWorkThread(new BrowserIORequest(
			BrowserIORequest.RENAME,this,
			session,vfs,from,to,null));

		VFSManager.runInAWTThread(new Runnable()
		{
			public void run()
			{
				endRequest();
			}
		});
	} //}}}

	//{{{ rename() method
	public void rename(String from, String newname)
	{
		VFS vfs = VFSManager.getVFSForPath(from);

		String filename = vfs.getFileName(from);
		String[] args = { filename };
		String to = newname;

		if(to == null || filename.equals(newname))
			return;

		to = MiscUtilities.constructPath(vfs.getParentOfPath(from),to);

		Object session = vfs.createVFSSession(from,this);
		if(session == null)
			return;

		if(!startRequest())
			return;

		VFSManager.runInWorkThread(new BrowserIORequest(
			BrowserIORequest.RENAME,this,
			session,vfs,from,to,null));

		VFSManager.runInAWTThread(new Runnable()
		{
			public void run()
			{
				endRequest();
			}
		});
	} //}}}

	//{{{ mkdir() method
	public void mkdir()
	{
		String newDirectory = GUIUtilities.input(this,"vfs.browser.mkdir",null);
		if(newDirectory == null)
			return;

		// if a directory is selected, create new dir in there.
		// if a file is selected, create new dir inside its parent.
		final VFSFile[] selected = getSelectedFiles();
		String parent;
		if(selected.length == 0)
			parent = path;
		else if(selected[0].getType() == VFSFile.FILE)
		{
			parent = selected[0].getPath();
			parent = VFSManager.getVFSForPath(parent)
				.getParentOfPath(parent);
		}
		else
			parent = selected[0].getPath();

		VFS vfs = VFSManager.getVFSForPath(parent);

		// path is the currently viewed directory in the browser
		newDirectory = MiscUtilities.constructPath(parent,newDirectory);

		Object session = vfs.createVFSSession(newDirectory,this);
		if(session == null)
			return;

		if(!startRequest())
			return;

		VFSManager.runInWorkThread(new BrowserIORequest(
			BrowserIORequest.MKDIR,this,
			session,vfs,newDirectory,null,null));

		VFSManager.runInAWTThread(new Runnable()
		{
			public void run()
			{
				endRequest();
				if (selected.length != 0 && selected[0].getType() != VFSFile.FILE)
				{
					VFSDirectoryEntryTable directoryEntryTable = browserView.getTable();
					int selectedRow = directoryEntryTable.getSelectedRow();
					VFSDirectoryEntryTableModel model = (VFSDirectoryEntryTableModel) directoryEntryTable.getModel();
					VFSDirectoryEntryTableModel.Entry entry = model.files[selectedRow];
					if (!entry.expanded)
					{
						browserView.clearExpansionState();
						browserView.loadDirectory(entry,entry.dirEntry.getPath(),
							false);
					}
				}
			}
		});
	} //}}}

	//{{{ newFile() method
	/**
	 * Creates a new file in the current directory.
	 * @since jEdit 4.0pre2
	 */
	public void newFile()
	{
		VFSFile[] selected = getSelectedFiles();
		if(selected.length >= 1)
		{
			VFSFile file = selected[0];
			if(file.getType() == VFSFile.DIRECTORY)
				jEdit.newFile(view,file.getPath());
			else
			{
				VFS vfs = VFSManager.getVFSForPath(file.getPath());
				jEdit.newFile(view,vfs.getParentOfPath(file.getPath()));
			}
		}
		else
			jEdit.newFile(view,path);
	} //}}}

	//{{{ fileProperties() method
	/**
	 * Show selected file's properties.
	 */
	public void fileProperties(VFSFile[] files)
	{
		new FilePropertiesDialog(view, this, files);
	} //}}}

	//{{{ searchInDirectory() method
	/**
	 * Opens a directory search in the current directory.
	 * @since jEdit 4.0pre2
	 */
	public void searchInDirectory()
	{
		VFSFile[] selected = getSelectedFiles();
		if(selected.length >= 1)
		{
			VFSFile file = selected[0];
			searchInDirectory(file.getPath(),file.getType() != VFSFile.FILE);
		}
		else
		{
			searchInDirectory(path,true);
		}
	} //}}}

	//{{{ searchInDirectory() method
	/**
	 * Opens a directory search in the specified directory.
	 * @param path The path name
	 * @param directory True if the path is a directory, false if it is a file
	 * @since jEdit 4.2pre1
	 */
	public void searchInDirectory(String path, boolean directory)
	{
		String filter;
		VFSFileFilter vfsff = getVFSFileFilter();
		if (vfsff instanceof GlobVFSFileFilter)
			filter = ((GlobVFSFileFilter)vfsff).getGlob();
		else
			filter = "*";

		if (!directory)
		{
			String name = MiscUtilities.getFileName(path);
			String ext = MiscUtilities.getFileExtension(name);
			filter = (ext == null || ext.length() == 0
				? filter : '*' + ext);
			path = MiscUtilities.getParentOfPath(path);
		}

		SearchAndReplace.setSearchFileSet(new DirectoryListSet(
			path,filter,true));
		SearchDialog.showSearchDialog(view,null,SearchDialog.DIRECTORY);
	} //}}}

	//{{{ getBrowserView() method
	BrowserView getBrowserView()
	{
		return browserView;
	} //}}}

	//{{{ getSelectedFiles() method
	/**
	 * @since jEdit 4.3pre2
	 */
	public VFSFile[] getSelectedFiles()
	{
		return browserView.getSelectedFiles();
	} //}}}

	//{{{ locateFile() method
	/**
	 * Goes to the given file's directory and selects the file in the list.
	 * @param path The file
	 * @since jEdit 4.2pre2
	 */
	public void locateFile(final String path)
	{
		VFSFileFilter filter = getVFSFileFilter();
		if(!filter.accept(MiscUtilities.getFileName(path)))
			setFilenameFilter(null);

		setDirectory(MiscUtilities.getParentOfPath(path));
		VFSManager.runInAWTThread(new Runnable()
		{
			public void run()
			{
				browserView.getTable().selectFile(path);
			}
		});
	} //}}}

	//{{{ createPluginsMenu() method
	public JComponent createPluginsMenu(JComponent pluginMenu, boolean showManagerOptions)
	{
		ActionHandler actionHandler = new ActionHandler();
		if(showManagerOptions && getMode() == BROWSER)
		{
			pluginMenu.add(GUIUtilities.loadMenuItem("plugin-manager",false));
			pluginMenu.add(GUIUtilities.loadMenuItem("plugin-options",false));
			if (pluginMenu instanceof JMenu)
				((JMenu)pluginMenu).addSeparator();
			else if (pluginMenu instanceof JPopupMenu)
				((JPopupMenu)pluginMenu).addSeparator();

		}
		else
			/* we're in a modal dialog */;

		List<JMenuItem> vec = new ArrayList<JMenuItem>();

		//{{{ old API
		Enumeration<VFS> e = VFSManager.getFilesystems();

		while(e.hasMoreElements())
		{
			VFS vfs = e.nextElement();
			if((vfs.getCapabilities() & VFS.BROWSE_CAP) == 0)
				continue;

				JMenuItem menuItem = new JMenuItem(jEdit.getProperty(
						"vfs." + vfs.getName() + ".label"));
				menuItem.setActionCommand(vfs.getName());
				menuItem.addActionListener(actionHandler);
				vec.add(menuItem);
		} //}}}

		//{{{ new API
		EditPlugin[] plugins = jEdit.getPlugins();
		for (int i = 0; i < plugins.length; i++)
		{
			JMenuItem menuItem = plugins[i].createBrowserMenuItems();
			if(menuItem != null)
				vec.add(menuItem);
		} //}}}

		if (!vec.isEmpty())
		{
			Collections.sort(vec,new MenuItemTextComparator());
			for(int i = 0; i < vec.size(); i++)
				pluginMenu.add(vec.get(i));
		}
		else
		{
			JMenuItem mi = new JMenuItem(jEdit.getProperty(
					"vfs.browser.plugins.no-plugins.label"));
			mi.setEnabled(false);
			pluginMenu.add(mi);
		}

		return pluginMenu;
	} //}}}

	//{{{ addBrowserListener() method
	public void addBrowserListener(BrowserListener l)
	{
		listenerList.add(BrowserListener.class,l);
	} //}}}

	//{{{ removeBrowserListener() method
	public void removeBrowserListener(BrowserListener l)
	{
		listenerList.remove(BrowserListener.class,l);
	} //}}}

	//{{{ filesActivated() method
	// canDoubleClickClose set to false when ENTER pressed
	public static final int M_OPEN = 0;
	public static final int M_OPEN_NEW_VIEW = 1;
	public static final int M_OPEN_NEW_PLAIN_VIEW = 2;
	public static final int M_OPEN_NEW_SPLIT = 3;
	public static final int M_INSERT = 4;

	/**
	 * This method does the "double-click" handling. It is public so that
	 * <code>browser.actions.xml</code> can bind to it.
	 * @since jEdit 4.2pre2
	 */
	public void filesActivated(int mode, boolean canDoubleClickClose)
	{
		VFSFile[] selectedFiles = browserView.getSelectedFiles();

		Buffer buffer = null;

check_selected: for(int i = 0; i < selectedFiles.length; i++)
		{
			VFSFile file = selectedFiles[i];

			if(file.getType() == VFSFile.DIRECTORY
				|| file.getType() == VFSFile.FILESYSTEM)
			{
				if(mode == M_OPEN_NEW_VIEW && this.mode == BROWSER)
					browseDirectoryInNewWindow(view,file.getPath());
				else
					setDirectory(file.getPath());
			}
			else if(this.mode == BROWSER || this.mode == BROWSER_DIALOG)
			{
				if(mode == M_INSERT)
				{
					view.getBuffer().insertFile(view,
						file.getPath());
					continue check_selected;
				}

				Buffer _buffer = jEdit.getBuffer(file.getPath());
				if(_buffer == null)
				{
					Hashtable<String, Object> props = new Hashtable<String, Object>();
					props.put(JEditBuffer.ENCODING,currentEncoding);
					props.put(Buffer.ENCODING_AUTODETECT,
						  Boolean.valueOf(autoDetectEncoding));
					_buffer = jEdit.openFile(view, null,
						file.getPath(),false,props);
				}
				else if(doubleClickClose && canDoubleClickClose
					&& this.mode != BROWSER_DIALOG
					&& selectedFiles.length == 1)
				{
					// close if this buffer is currently
					// visible in the view.
					EditPane[] editPanes = view.getEditPanes();
					for(int j = 0; j < editPanes.length; j++)
					{
						if(editPanes[j].getBuffer() == _buffer)
						{
							jEdit.closeBuffer(view,_buffer);
							return;
						}
					}
				}

				if(_buffer != null)
					buffer = _buffer;
			}
			else
			{
				// if a file is selected in OPEN_DIALOG or
				// SAVE_DIALOG mode, just let the listener(s)
				// handle it
			}
		}

		if(buffer != null)
		{
			switch(mode)
			{
			case M_OPEN:
				view.setBuffer(buffer);
				break;
			case M_OPEN_NEW_VIEW:
				jEdit.newView(view,buffer,false);
				break;
			case M_OPEN_NEW_PLAIN_VIEW:
				jEdit.newView(view,buffer,true);
				break;
			case M_OPEN_NEW_SPLIT:
				view.splitHorizontally().setBuffer(buffer);
				break;
			}
		}

		Object[] listeners = listenerList.getListenerList();
		for(int i = 0; i < listeners.length; i++)
		{
			if(listeners[i] == BrowserListener.class)
			{
				BrowserListener l = (BrowserListener)listeners[i+1];
				l.filesActivated(this,selectedFiles);
			}
		}
	} //}}}

	//{{{ move() method
	public void move(String newPosition)
	{
		boolean horz = (mode != BROWSER
				|| DockableWindowManager.TOP.equals(newPosition)
				|| DockableWindowManager.BOTTOM.equals(newPosition));
		if (horz == horizontalLayout)
			return;
		horizontalLayout = horz;
		topBox.remove(toolbarBox);
		toolbarBox = new Box(horizontalLayout
				? BoxLayout.X_AXIS
				: BoxLayout.Y_AXIS);
		topBox.add(toolbarBox, 0);
		propertiesChanged();
	} //}}}

	//{{{ Package-private members
	String currentEncoding;
	boolean autoDetectEncoding;

	//{{{ directoryLoaded() method
	void directoryLoaded(Object node, Object[] loadInfo,
		boolean addToHistory)
	{
		VFSManager.runInAWTThread(new DirectoryLoadedAWTRequest(
			node,loadInfo,addToHistory));
	} //}}}

	//{{{ filesSelected() method
	void filesSelected()
	{
		VFSFile[] selectedFiles = browserView.getSelectedFiles();

		if(mode == BROWSER)
		{
			for(int i = 0; i < selectedFiles.length; i++)
			{
				VFSFile file = selectedFiles[i];
				Buffer buffer = jEdit.getBuffer(file.getPath());
				if(buffer != null && view != null)
					view.setBuffer(buffer);
			}
		}

		Object[] listeners = listenerList.getListenerList();
		for(int i = 0; i < listeners.length; i++)
		{
			if(listeners[i] == BrowserListener.class)
			{
				BrowserListener l = (BrowserListener)listeners[i+1];
				l.filesSelected(this,selectedFiles);
			}
		}
	} //}}}

	//{{{ endRequest() method
	void endRequest()
	{
		requestRunning = false;
	} //}}}

	//}}}

	//{{{ Private members

	private static final ActionContext actionContext;

	static
	{
		actionContext = new BrowserActionContext();

		ActionSet builtInActionSet = new ActionSet(null,null,null,
			jEdit.class.getResource("browser.actions.xml"));
		builtInActionSet.setLabel(jEdit.getProperty("action-set.browser"));
		builtInActionSet.load();
		actionContext.addActionSet(builtInActionSet);
	}

	//{{{ Instance variables
	private EventListenerList listenerList;
	private View view;
	private boolean horizontalLayout;
	private String path;
	private JPanel pathAndFilterPanel;
	private HistoryTextField pathField;
	private JComponent defaultFocusComponent;
	private JCheckBox filterCheckbox;
	private HistoryComboBoxEditor filterEditor;
	private JComboBox filterField;
	private Box toolbarBox;
	private Box topBox;
	private FavoritesMenuButton favorites;
	private PluginsMenuButton plugins;
	private BrowserView browserView;
	private int mode;
	private boolean multipleSelection;

	private boolean showHiddenFiles;
	private boolean sortMixFilesAndDirs;
	private boolean sortIgnoreCase;
	private boolean doubleClickClose;

	private boolean requestRunning;
	private boolean maybeReloadRequestRunning;

	private Stack<String> historyStack = new Stack<String>();
	private Stack<String> nextDirectoryStack = new Stack<String>();
	//}}}

	//{{{ createMenuBar() method
	private Container createMenuBar()
	{
		JToolBar menuBar = new JToolBar();
		menuBar.setFloatable(false);

		menuBar.add(new CommandsMenuButton());
		menuBar.add(Box.createHorizontalStrut(3));
		menuBar.add(plugins = new PluginsMenuButton());
		menuBar.add(Box.createHorizontalStrut(3));
		menuBar.add(favorites = new FavoritesMenuButton());

		return menuBar;
	} //}}}

	//{{{ createToolBar() method
	private Container createToolBar()
	{
		if(mode == BROWSER)
			return GUIUtilities.loadToolBar(actionContext,
				"vfs.browser.toolbar-browser");
		else
			return GUIUtilities.loadToolBar(actionContext,
				"vfs.browser.toolbar-dialog");
	} //}}}

	//{{{ propertiesChanged() method
	private void propertiesChanged()
	{
		showHiddenFiles = jEdit.getBooleanProperty("vfs.browser.showHiddenFiles");
		sortMixFilesAndDirs = jEdit.getBooleanProperty("vfs.browser.sortMixFilesAndDirs");
		sortIgnoreCase = jEdit.getBooleanProperty("vfs.browser.sortIgnoreCase");
		doubleClickClose = jEdit.getBooleanProperty("vfs.browser.doubleClickClose");

		browserView.propertiesChanged();

		toolbarBox.removeAll();

		if(jEdit.getBooleanProperty("vfs.browser.showToolbar"))
		{
			Container toolbar = createToolBar();
			if(horizontalLayout)
				toolbarBox.add(toolbar);
			else
			{
				toolbarBox.add(toolbar);
			}
		}

		if(jEdit.getBooleanProperty("vfs.browser.showMenubar"))
		{
			Container menubar = createMenuBar();
			if(horizontalLayout)
			{
				toolbarBox.add(menubar,0);
			}
			else
			{
				menubar.add(Box.createGlue());
				toolbarBox.add(menubar);
			}
		}
		else
		{
			plugins = null;
			favorites = null;
		}

		revalidate();

		if(path != null)
			reloadDirectory();
	} //}}}

	/* We do this stuff because the browser is not able to handle
	 * more than one request yet */

	//{{{ startRequest() method
	private boolean startRequest()
	{
		if(requestRunning)
		{
			// dump stack trace for debugging purposes
			Log.log(Log.DEBUG,this,new Throwable("For debugging purposes"));

			GUIUtilities.error(this,"browser-multiple-io",null);
			return false;
		}
		else
		{
			requestRunning = true;
			return true;
		}
	} //}}}

	//{{{ updateFilterEnabled() method
	private void updateFilterEnabled()
	{
		filterField.setEnabled(filterCheckbox.isSelected());
		filterEditor.setEnabled(filterCheckbox.isSelected());
	} //}}}

	//{{{ maybeReloadDirectory() method
	private void maybeReloadDirectory(String dir)
	{
		if(MiscUtilities.isURL(dir)
			&& MiscUtilities.getProtocolOfURL(dir).equals(
			FavoritesVFS.PROTOCOL))
		{
			if(favorites != null)
				favorites.popup = null;
		}

		// this is a dirty hack and it relies on the fact
		// that updates for parents are sent before updates
		// for the changed nodes themselves (if this was not
		// the case, the browser wouldn't be updated properly
		// on delete, etc).
		//
		// to avoid causing '> 1 request' errors, don't reload
		// directory if request already active
		if(maybeReloadRequestRunning)
		{
			//Log.log(Log.WARNING,this,"VFS update: request already in progress");
			return;
		}

		// save a file -> sends vfs update. if a VFS file dialog box
		// is shown from the same event frame as the save, the
		// VFSUpdate will be delivered before the directory is loaded,
		// and before the path is set.
		if(path != null)
		{
			try
			{
				maybeReloadRequestRunning = true;

				browserView.maybeReloadDirectory(dir);
			}
			finally
			{
				VFSManager.runInAWTThread(new Runnable()
				{
					public void run()
					{
						maybeReloadRequestRunning = false;
					}
				});
			}
		}
	} //}}}

	//}}}

	//{{{ Inner classes

	//{{{ ActionHandler class
	class ActionHandler implements ActionListener, ItemListener
	{
		public void actionPerformed(ActionEvent evt)
		{
			if (isProcessingEvent)
				return;

			Object source = evt.getSource();

			if (source == pathField
			    || source == filterCheckbox)
			{
				isProcessingEvent = true;
				resetLater();

				updateFilterEnabled();

				String p = pathField.getText();

				if(p != null)
					setDirectory(p);
				browserView.focusOnFileView();
			}

			else if (source == filterField.getEditor())
			{
				// force the editor to refresh.
				filterField.getEditor().setItem(
					filterField.getEditor().getItem());
			}

			// depending on Swing look & feel, filterField.getEditor()
			// returns some ComboBoxUI
			else if (source == filterEditor)
			{
				// force the editor to refresh.
				filterEditor.setItem(
					filterEditor.getItem());
				filterField.setSelectedItem(
					filterEditor.getItem());
				// ### ugly:
				// itemStateChanged does not seem to get fired
				itemStateChanged(new ItemEvent(filterField,
					ItemEvent.ITEM_STATE_CHANGED,
					filterEditor.getItem(),
					ItemEvent.SELECTED));
			}
		}

		public void itemStateChanged(ItemEvent e)
		{
			if (isProcessingEvent)
				return;

			if (e.getStateChange() != ItemEvent.SELECTED)
				return;

			isProcessingEvent = true;
			resetLater();

			filterField.setEditable(e.getItem() instanceof GlobVFSFileFilter);
			updateFilterEnabled();
			String path = pathField.getText();
			if(path != null)
				setDirectory(path);

			browserView.focusOnFileView();
		}

		/**
		 * Why this method exists: since both actionPerformed()
		 * and itemStateChanged() above can change the combo box,
		 * executing one of them can cause a chain reaction causing
		 * the other method to be called. This would cause the
		 * VFS subsystem to be called several times, which would
		 * cause a warning to show up if the first operation is
		 * still in progress, or cause a second operation to happen
		 * which is not really wanted especially if we're talking
		 * about a remove VFS. So the methods set a flag saying
		 * that something is going on, and this method resets
		 * the flag after the AWT thread is done with the
		 * current events.
		 */
		private void resetLater()
		{
			SwingUtilities.invokeLater(
				new Runnable()
				{
					public void run()
					{
						isProcessingEvent = false;
					}
				}
			);
		}

		private boolean isProcessingEvent;

	} //}}}

	//{{{ CommandsMenuButton class
	class CommandsMenuButton extends RolloverButton
	{
		//{{{ CommandsMenuButton constructor
		CommandsMenuButton()
		{
			setText(jEdit.getProperty("vfs.browser.commands.label"));
			setIcon(GUIUtilities.loadIcon(jEdit.getProperty("dropdown-arrow.icon")));
			setHorizontalTextPosition(SwingConstants.LEADING);
			setName("commands");

			popup = new BrowserCommandsMenu(VFSBrowser.this,null);

			CommandsMenuButton.this.setRequestFocusEnabled(false);
			setMargin(new Insets(1,1,1,1));
			CommandsMenuButton.this.addMouseListener(new MouseHandler());

			if(OperatingSystem.isMacOSLF())
				CommandsMenuButton.this.putClientProperty("JButton.buttonType","toolbar");
		} //}}}

		BrowserCommandsMenu popup;

		//{{{ MouseHandler class
		class MouseHandler extends MouseAdapter
		{
			@Override
			public void mousePressed(MouseEvent evt)
			{
				if(!popup.isVisible())
				{
					popup.update();

					GUIUtilities.showPopupMenu(
						popup,CommandsMenuButton.this,0,
						CommandsMenuButton.this.getHeight(),
						false);
				}
				else
				{
					popup.setVisible(false);
				}
			}
		} //}}}
	} //}}}

	//{{{ PluginsMenuButton class
	class PluginsMenuButton extends RolloverButton
	{
		//{{{ PluginsMenuButton constructor
		PluginsMenuButton()
		{
			setText(jEdit.getProperty("vfs.browser.plugins.label"));
			setIcon(GUIUtilities.loadIcon(jEdit.getProperty("dropdown-arrow.icon")));
			setHorizontalTextPosition(SwingConstants.LEADING);
			setName("plugins");

			PluginsMenuButton.this.setRequestFocusEnabled(false);
			setMargin(new Insets(1,1,1,1));
			PluginsMenuButton.this.addMouseListener(new MouseHandler());

			if(OperatingSystem.isMacOSLF())
				PluginsMenuButton.this.putClientProperty("JButton.buttonType","toolbar");
		} //}}}

		JPopupMenu popup;

		//{{{ updatePopupMenu() method
		void updatePopupMenu()
		{
			popup = null;
		} //}}}

		//{{{ createPopupMenu() method
		private void createPopupMenu()
		{
			if(popup != null)
				return;

			popup = (JPopupMenu)createPluginsMenu(new JPopupMenu(),true);
		} //}}}

		//{{{ MouseHandler class
		class MouseHandler extends MouseAdapter
		{
			@Override
			public void mousePressed(MouseEvent evt)
			{
				createPopupMenu();

				if(!popup.isVisible())
				{
					GUIUtilities.showPopupMenu(
						popup,PluginsMenuButton.this,0,
						PluginsMenuButton.this.getHeight(),
						false);
				}
				else
				{
					popup.setVisible(false);
				}
			}
		} //}}}
	} //}}}

	//{{{ FavoritesMenuButton class
	class FavoritesMenuButton extends RolloverButton
	{
		//{{{ FavoritesMenuButton constructor
		FavoritesMenuButton()
		{
			setText(jEdit.getProperty("vfs.browser.favorites.label"));
			setIcon(GUIUtilities.loadIcon(jEdit.getProperty("dropdown-arrow.icon")));
			setHorizontalTextPosition(SwingConstants.LEADING);
			setName("favorites");

			FavoritesMenuButton.this.setRequestFocusEnabled(false);
			setMargin(new Insets(1,1,1,1));
			FavoritesMenuButton.this.addMouseListener(new MouseHandler());

			if(OperatingSystem.isMacOSLF())
				FavoritesMenuButton.this.putClientProperty("JButton.buttonType","toolbar");
		} //}}}

		JPopupMenu popup;

		//{{{ createPopupMenu() method
		void createPopupMenu()
		{
			popup = new JPopupMenu();
			ActionHandler actionHandler = new ActionHandler();

			JMenuItem mi = new JMenuItem(
				jEdit.getProperty(
				"vfs.browser.favorites"
				+ ".add-to-favorites.label"));
			mi.setActionCommand("add-to-favorites");
			mi.addActionListener(actionHandler);
			popup.add(mi);

			mi = new JMenuItem(
				jEdit.getProperty(
				"vfs.browser.favorites"
				+ ".edit-favorites.label"));
			mi.setActionCommand("dir@favorites:");
			mi.addActionListener(actionHandler);
			popup.add(mi);

			popup.addSeparator();

			VFSFile[] favorites = FavoritesVFS.getFavorites();
			if(favorites.length == 0)
			{
				mi = new JMenuItem(
					jEdit.getProperty(
					"vfs.browser.favorites"
					+ ".no-favorites.label"));
				mi.setEnabled(false);
				popup.add(mi);
			}
			else
			{
				Arrays.sort(favorites,
					new VFS.DirectoryEntryCompare(
					sortMixFilesAndDirs,
					sortIgnoreCase));
				for(int i = 0; i < favorites.length; i++)
				{
					VFSFile favorite = favorites[i];
					mi = new JMenuItem(favorite.getPath());
					mi.setIcon(FileCellRenderer
						.getIconForFile(
						favorite,false));
					String cmd = (favorite.getType() ==
						VFSFile.FILE
						? "file@" : "dir@")
						+ favorite.getPath();
					mi.setActionCommand(cmd);
					mi.addActionListener(actionHandler);
					popup.add(mi);
				}
			}
		} //}}}

		//{{{ ActionHandler class
		class ActionHandler implements ActionListener
		{
			public void actionPerformed(ActionEvent evt)
			{
				String actionCommand = evt.getActionCommand();
				if("add-to-favorites".equals(actionCommand))
				{
					// if any directories are selected, add
					// them, otherwise add current directory
					VFSFile[] selected = getSelectedFiles();
					if(selected == null || selected.length == 0)
					{
						if(path.equals(FavoritesVFS.PROTOCOL + ':'))
						{
							GUIUtilities.error(VFSBrowser.this,
								"vfs.browser.recurse-favorites",
								null);
						}
						else
						{
							FavoritesVFS.addToFavorites(path,
								VFSFile.DIRECTORY);
						}
					}
					else
					{
						for(int i = 0; i < selected.length; i++)
						{
							VFSFile file = selected[i];
							FavoritesVFS.addToFavorites(file.getPath(),
								file.getType());
						}
					}
				}
				else if(actionCommand.startsWith("dir@"))
				{
					setDirectory(actionCommand.substring(4));
				}
				else if(actionCommand.startsWith("file@"))
				{
					switch(getMode())
					{
					case BROWSER:
						jEdit.openFile(view,actionCommand.substring(5));
						break;
					default:
						locateFile(actionCommand.substring(5));
						break;
					}
				}
			}
		} //}}}

		//{{{ MouseHandler class
		class MouseHandler extends MouseAdapter
		{
			@Override
			public void mousePressed(MouseEvent evt)
			{
				if(popup != null && popup.isVisible())
				{
					popup.setVisible(false);
					return;
				}

				if(popup == null)
					createPopupMenu();

				GUIUtilities.showPopupMenu(
					popup,FavoritesMenuButton.this,0,
					FavoritesMenuButton.this.getHeight(),
					false);
			}
		} //}}}
	} //}}}

	//{{{ DirectoryLoadedAWTRequest class
	class DirectoryLoadedAWTRequest implements Runnable
	{
		private Object node;
		private Object[] loadInfo;
		private boolean addToHistory;

		DirectoryLoadedAWTRequest(Object node, Object[] loadInfo,
			boolean addToHistory)
		{
			this.node = node;
			this.loadInfo = loadInfo;
			this.addToHistory = addToHistory;
		}

		public void run()
		{
			String path = (String)loadInfo[0];
			if(path == null)
			{
				// there was an error
				return;
			}

			VFSFile[] list = (VFSFile[])loadInfo[1];

			if(node == null)
			{
				// This is the new, canonical path
				VFSBrowser.this.path = path;
				if(!pathField.getText().equals(path))
					pathField.setText(path);
				if(path.endsWith("/") ||
					path.endsWith(File.separator))
				{
					// ensure consistent history;
					// eg we don't want both
					// foo/ and foo
					path = path.substring(0,
						path.length() - 1);
				}

				if(addToHistory)
				{
					HistoryModel.getModel("vfs.browser.path")
						.addItem(path);
				}
			}

			boolean filterEnabled = filterCheckbox.isSelected();

			List<VFSFile> directoryList = new ArrayList<VFSFile>();

			int directories = 0;
			int files = 0;
			int invisible = 0;

			if(list != null)
			{
				VFSFileFilter filter = getVFSFileFilter();

				for(int i = 0; i < list.length; i++)
				{
					VFSFile file = list[i];
					if(file.isHidden() && !showHiddenFiles)
					{
						invisible++;
						continue;
					}

					if (filter != null && (filterEnabled || filter instanceof DirectoriesOnlyFilter)
					    && !filter.accept(file))
					{
						invisible++;
						continue;
					}

					if(file.getType() == VFSFile.FILE)
						files++;
					else
						directories++;

					directoryList.add(file);
				}

				Collections.sort(directoryList,
					new VFS.DirectoryEntryCompare(
					sortMixFilesAndDirs,
					sortIgnoreCase));
			}

			browserView.directoryLoaded(node,path,
				directoryList);

			// to notify listeners that any existing
			// selection has been deactivated

			// turns out under some circumstances this
			// method can switch the current buffer in
			// BROWSER mode.

			// in any case, this is only needed for the
			// directory chooser (why?), so we add a
			// check. otherwise poor Rick will go insane.
			if(mode == CHOOSE_DIRECTORY_DIALOG)
				filesSelected();
		}

		@Override
		public String toString()
		{
			return (String)loadInfo[0];
		}
	} //}}}

	//{{{ BrowserActionContext class
	static class BrowserActionContext extends ActionContext
	{
		/**
		 * If event source hierarchy contains a VFSDirectoryEntryTable,
		 * this is the currently selected files there. Otherwise, this
		 * is the currently selected item in the parent directory list.
		 */
		private static VFSFile[] getSelectedFiles(EventObject evt,
			VFSBrowser browser)
		{
			Component source = (Component)evt.getSource();

			if(GUIUtilities.getComponentParent(source, BrowserView.ParentDirectoryList.class)
				!= null)
			{
				Object[] selected = browser.getBrowserView()
					.getParentDirectoryList()
					.getSelectedValues();
				VFSFile[] returnValue = new VFSFile[
					selected.length];
				System.arraycopy(selected,0,returnValue,0,
					selected.length);
				return returnValue;
			}
			else
			{
				return browser.getSelectedFiles();
			}
		}

		@Override
		public void invokeAction(EventObject evt, EditAction action)
		{
			VFSBrowser browser = (VFSBrowser)
				GUIUtilities.getComponentParent(
				(Component)evt.getSource(),
				VFSBrowser.class);

			VFSFile[] files = getSelectedFiles(evt,browser);

			// in the future we will want something better,
			// eg. having an 'evt' object passed to
			// EditAction.invoke().

			// for now, since all browser actions are
			// written in beanshell we set the 'browser'
			// variable directly.
			NameSpace global = BeanShell.getNameSpace();
			try
			{
				global.setVariable("browser",browser);
				global.setVariable("files",files);

				View view = browser.getView();
				// I guess ideally all browsers
				// should have views, but since they
				// don't, we just use the active view
				// in that case, since some actions
				// depend on a view being there and
				// I don't want to add checks to
				// them all
				if(view == null)
					view = jEdit.getActiveView();
				action.invoke(view);
			}
			catch(UtilEvalError err)
			{
				Log.log(Log.ERROR,this,err);
			}
			finally
			{
				try
				{
					global.setVariable("browser",null);
					global.setVariable("files",null);
				}
				catch(UtilEvalError err)
				{
					Log.log(Log.ERROR,this,err);
				}
			}
		}
	} //}}}

	//{{{ HistoryComboBoxEditor class
	private static class HistoryComboBoxEditor
				extends HistoryTextField
				implements ComboBoxEditor
	{

		HistoryComboBoxEditor(String key)
		{
			super(key);
		}

		public Object getItem()
		{
			if (current == null)
			{
				current = new GlobVFSFileFilter(getText());
			}

			if (!current.getGlob().equals(getText()))
			{
				current.setGlob(getText());
			}

			return current;
		}

		public void setItem(Object item)
		{
			if (item == current)
			{
				// if we keep the same object, swing
				// will cause an event to be fired
				// on the default button of the dialog,
				// causing a beep since no file is
				// selected...
				if (item != null)
				{
					GlobVFSFileFilter filter = (GlobVFSFileFilter) item;
					current = new GlobVFSFileFilter(filter.getGlob());
					setText(current.getGlob());
				}
				return;
			}

			// this happens when changing the selected item
			// in the combo; the combo has not yet fired an
			// itemStateChanged() event, so it's not put into
			// non-editable mode by the handler above.
			if (!(item instanceof GlobVFSFileFilter))
				return;

			if (item != null)
			{
				GlobVFSFileFilter filter = (GlobVFSFileFilter) item;
				filter = new GlobVFSFileFilter(filter.getGlob());
				setText(filter.getGlob());
				addCurrentToHistory();
				current = filter;
			}
			else
			{
				setText("*");
				current = new GlobVFSFileFilter("*");
			}
		}

		@Override
		protected void processFocusEvent(FocusEvent e)
		{
			// AWT will call setItem() when the editor loses
			// focus; that can cause weird and unwanted things
			// to happen, so ignore lost focus events.
			if (e.getID() != FocusEvent.FOCUS_LOST)
				super.processFocusEvent(e);
			else
			{
				setCaretPosition(0);
				getCaret().setVisible(false);
			}
		}

		public Component getEditorComponent()
		{
			return this;
		}

		private GlobVFSFileFilter current;

	} //}}}

	//{{{ VFSFileFilterRenderer class
	private static class VFSFileFilterRenderer extends DefaultListCellRenderer
	{

		@Override
		public Component getListCellRendererComponent(JList list,
			Object value, int index, boolean isSelected,
			boolean cellHasFocus)
		{
			assert value instanceof VFSFileFilter : "Filter is not a VFSFileFilter";
			super.getListCellRendererComponent(
				list, value, index, isSelected, cellHasFocus);
			setText(((VFSFileFilter)value).getDescription());
			return this;
		}

	} //}}}

	//{{{ DirectoriesOnlyFilter class
	public static class DirectoriesOnlyFilter implements VFSFileFilter
	{

		public boolean accept(VFSFile file)
		{
			return file.getType() == VFSFile.DIRECTORY
				|| file.getType() == VFSFile.FILESYSTEM;
		}

		public boolean accept(String url)
		{
			return false;
		}

		public String getDescription()
		{
			return jEdit.getProperty("vfs.browser.file_filter.dir_only");

	public View getView()
	{
		return view;

FUNC_DECL RETURN IF RETURN BINARY_OP CALL CALL

FUNC_DECL IF PARAM BINARY_OP CALL VAR_DECL VAR_DECL LITERAL_ANCHOR CALL CALL CALL CALL CALL CALL ASSIGN ASSIGN ASSIGN ASSIGN CALL CALL LITERAL_ANCHOR LITERAL_ANCHOR CALL LITERAL_ANCHOR CALL

FUNC_DECL IF BINARY_OP LITERAL_ANCHOR CALL CALL CALL CALL CALL CALL

FUNC_DECL IF IF PARAM CALL ASSIGN CALL CALL CALL RETURN CALL CALL ASSIGN CALL CALL LITERAL_ANCHOR LITERAL_ANCHOR CALL ASSIGN LITERAL_ANCHOR FUNC_DECL CALL LITERAL_ANCHOR CALL

FUNC_DECL IF VAR_DECL PARAM CALL CALL CALL CALL CALL CALL CALL CALL FUNC_DECL LITERAL_ANCHOR CALL CALL

FUNC_DECL RETURN VAR_DECL CALL CALL CALL CALL CALL CALL ASSIGN CALL ASSIGN CALL CALL LITERAL_ANCHOR CALL CALL LITERAL_ANCHOR CALL LITERAL_ANCHOR

FUNC_DECL IF IF IF BINARY_OP CALL CALL CALL CALL CALL ASSIGN ASSIGN ASSIGN ASSIGN CALL LITERAL_ANCHOR IF VAR_DECL LITERAL_ANCHOR IF VAR_DECL LITERAL_ANCHOR CALL CALL CALL CALL ASSIGN ASSIGN LITERAL_ANCHOR LITERAL_ANCHOR LITERAL_ANCHOR LITERAL_ANCHOR LITERAL_ANCHOR LITERAL_ANCHOR CALL CALL CALL CALL CALL CALL CALL CALL LITERAL_ANCHOR


org.apache.tools.ant.PathTokenizer

public class PathTokenizer {
    /**
     * A tokenizer to break the string up based on the ':' or ';' separators.
     */
    private StringTokenizer tokenizer;

    /**
     * A String which stores any path components which have been read ahead
     * due to DOS filesystem compensation.
     */
    private String lookahead = null;

    /**
     * A boolean that determines if we are running on Novell NetWare, which
     * exhibits slightly different path name characteristics (multi-character
     * volume / drive names)
     */
    private boolean onNetWare = Os.isFamily("netware");

    /**
     * Flag to indicate whether or not we are running on a platform with a
     * DOS style filesystem
     */
    private boolean dosStyleFilesystem;

    /**
     * Constructs a path tokenizer for the specified path.
     *
     * @param path The path to tokenize. Must not be null.
     */
    public PathTokenizer(String path) {
        if (onNetWare) {
            tokenizer = new StringTokenizer(path, ":;", true);
        } else {
            tokenizer = new StringTokenizer(path, ":;", false);
        }
        dosStyleFilesystem = File.pathSeparatorChar == ';';
    }

    /**
     * Tests if there are more path elements available from this tokenizer's
     * path. If this method returns true, then a subsequent call
     * to nextToken will successfully return a token.
     *
     * @return true if and only if there is at least one token
     * in the string after the current position; false otherwise.
     */
    public boolean hasMoreTokens() {
        if (lookahead != null) {
            return true;
        }

        return tokenizer.hasMoreTokens();
    }

    /**
     * Returns the next path element from this tokenizer.
     *
     * @return the next path element from this tokenizer.
     *
     * @exception NoSuchElementException if there are no more elements in this
     *            tokenizer's path.
     */
    public String nextToken() throws NoSuchElementException {
        String token = null;
        if (lookahead != null) {
            token = lookahead;
            lookahead = null;
        } else {
            token = tokenizer.nextToken().trim();
        }

        if (!onNetWare) {
            if (token.length() == 1 && Character.isLetter(token.charAt(0))
                                    && dosStyleFilesystem
                                    && tokenizer.hasMoreTokens()) {
                String nextToken = tokenizer.nextToken().trim();
                if (nextToken.startsWith("\\") || nextToken.startsWith("/")) {
                    token += ":" + nextToken;
                } else {
                    lookahead = nextToken;
                }
            }
        } else {
            if (token.equals(File.pathSeparator) || token.equals(":")) {
                token = tokenizer.nextToken().trim();
            }

            if (tokenizer.hasMoreTokens()) {
                String nextToken = tokenizer.nextToken().trim();

                if (!nextToken.equals(File.pathSeparator)) {
                    if (nextToken.equals(":")) {
                        if (!token.startsWith("/") && !token.startsWith("\\")
                            && !token.startsWith(".")
                            && !token.startsWith("..")) {
                            String oneMore = tokenizer.nextToken().trim();
                            if (!oneMore.equals(File.pathSeparator)) {
                                token += ":" + oneMore;
                            } else {
                                token += ":";
                                lookahead = oneMore;
                            }
                        }
                    } else {
                        lookahead = nextToken;
                    }
                }
            }
        }
        return token;


final class Predicate extends Expression implements Closure {

    /**
     * The predicate's expression.
     */
    private Expression _exp = null;

    /**
     * This flag indicates if optimizations are turned on. The
     * method dontOptimize() can be called to turn
     * optimizations off.
     */
    private boolean _canOptimize = true;

    /**
     * Flag indicatig if the nth position optimization is on. It
     * is set in typeCheck().
     */
    private boolean _nthPositionFilter = false;

    /**
     * Flag indicatig if the nth position descendant is on. It
     * is set in typeCheck().
     */
    private boolean _nthDescendant = false;

    /**
     * Cached node type of the expression that owns this predicate.
     */
    int _ptype = -1;

    /**
     * Name of the inner class.
     */
    private String _className = null;

    /**
     * List of variables in closure.
     */
    private ArrayList _closureVars = null;

    /**
     * Reference to parent closure.
     */
    private Closure _parentClosure = null;

    /**
     * Cached value of method getCompareValue().
     */
    private Expression _value = null;

    /**
     * Cached value of method getCompareValue().
     */
    private Step _step = null;

    /**
     * Initializes a predicate.
     */
    public Predicate(Expression exp) {
        _exp = exp;
        _exp.setParent(this);

    }

    /**
     * Set the parser for this expression.
     */
    public void setParser(Parser parser) {
	super.setParser(parser);
	_exp.setParser(parser);
    }

    /**
     * Returns a boolean value indicating if the nth position optimization
     * is on. Must be call after type checking!
     */
    public boolean isNthPositionFilter() {
	return _nthPositionFilter;
    }

    /**
     * Returns a boolean value indicating if the nth descendant optimization
     * is on. Must be call after type checking!
     */
    public boolean isNthDescendant() {
	return _nthDescendant;
    }

    /**
     * Turns off all optimizations for this predicate.
     */
    public void dontOptimize() {
	_canOptimize = false;
    }

    /**
     * Returns true if the expression in this predicate contains a call
     * to position().
     */
    public boolean hasPositionCall() {
	return _exp.hasPositionCall();
    }

    /**
     * Returns true if the expression in this predicate contains a call
     * to last().
     */
    public boolean hasLastCall() {
	return _exp.hasLastCall();
    }

    /**
     * Returns true if this closure is compiled in an inner class (i.e.
     * if this is a real closure).
     */
    public boolean inInnerClass() {
	return (_className != null);
    }

    /**
     * Returns a reference to its parent closure or null if outermost.
     */
    public Closure getParentClosure() {
	if (_parentClosure == null) {
	    SyntaxTreeNode node = getParent();
	    do {
		if (node instanceof Closure) {
		    _parentClosure = (Closure) node;
		    break;
		}
		if (node instanceof TopLevelElement) {
		}
		node = node.getParent();
	    } while (node != null);
	}
	return _parentClosure;
    }

    /**
     * Returns the name of the auxiliary class or null if this predicate
     * is compiled inside the Translet.
     */
    public String getInnerClassName() {
	return _className;
    }

    /**
     * Add new variable to the closure.
     */
    public void addVariable(VariableRefBase variableRef) {
	if (_closureVars == null) {
	    _closureVars = new ArrayList();
	}

	if (!_closureVars.contains(variableRef)) {
	    _closureVars.add(variableRef);

	    Closure parentClosure = getParentClosure();
	    if (parentClosure != null) {
		parentClosure.addVariable(variableRef);
	    }
	}
    }

    /**
     * Returns the node type of the expression owning this predicate. The
     * return value is cached in _ptype.
     */
    public int getPosType() {
	if (_ptype == -1) {
	    SyntaxTreeNode parent = getParent();
	    if (parent instanceof StepPattern) {
		_ptype = ((StepPattern)parent).getNodeType();
	    }
	    else if (parent instanceof AbsoluteLocationPath) {
		AbsoluteLocationPath path = (AbsoluteLocationPath)parent;
		Expression exp = path.getPath();
		if (exp instanceof Step) {
		    _ptype = ((Step)exp).getNodeType();
		}
	    }
	    else if (parent instanceof VariableRefBase) {
		final VariableRefBase ref = (VariableRefBase)parent;
		final VariableBase var = ref.getVariable();
		final Expression exp = var.getExpression();
		if (exp instanceof Step) {
		    _ptype = ((Step)exp).getNodeType();
		}
	    }
	    else if (parent instanceof Step) {
		_ptype = ((Step)parent).getNodeType();
	    }
	}
	return _ptype;
    }

    public boolean parentIsPattern() {
	return (getParent() instanceof Pattern);
    }

    public Expression getExpr() {
	return _exp;
    }

    public String toString() {
        return "pred(" + _exp + ')';
    }

    /**
     * Type check a predicate expression. If the type of the expression is
     * number convert it to boolean by adding a comparison with position().
     * Note that if the expression is a parameter, we cannot distinguish
     * at compile time if its type is number or not. Hence, expressions of
     * reference type are always converted to booleans.
     *
     * This method may be called twice, before and after calling
     * dontOptimize(). If so, the second time it should honor
     * the new value of _canOptimize.
     */
    public Type typeCheck(SymbolTable stable) throws TypeCheckError {
	Type texp = _exp.typeCheck(stable);

	if (texp instanceof ReferenceType) {
	    _exp = new CastExpr(_exp, texp = Type.Real);
	}

	if (texp instanceof ResultTreeType) {
	    _exp = new CastExpr(_exp, Type.Boolean);
	    _exp = new CastExpr(_exp, Type.Real);
	    texp = _exp.typeCheck(stable);
	}

	if (texp instanceof NumberType) {
	    if (texp instanceof IntType == false) {
		_exp = new CastExpr(_exp, Type.Int);
	    }

            if (_canOptimize) {
                _nthPositionFilter =
                    !_exp.hasLastCall() && !_exp.hasPositionCall();

                if (_nthPositionFilter) {
                    SyntaxTreeNode parent = getParent();
                    _nthDescendant = (parent instanceof Step) &&
                        (parent.getParent() instanceof AbsoluteLocationPath);
                    return _type = Type.NodeSet;
                }
            }

            _nthPositionFilter = _nthDescendant = false;

           final QName position =
                getParser().getQNameIgnoreDefaultNs("position");
           final PositionCall positionCall =
                new PositionCall(position);
           positionCall.setParser(getParser());
           positionCall.setParent(this);

           _exp = new EqualityExpr(Operators.EQ, positionCall,
                                    _exp);
           if (_exp.typeCheck(stable) != Type.Boolean) {
               _exp = new CastExpr(_exp, Type.Boolean);
           }
           return _type = Type.Boolean;
	}
	else {
	    if (texp instanceof BooleanType == false) {
		_exp = new CastExpr(_exp, Type.Boolean);
            }
            return _type = Type.Boolean;
	}
    }

    /**
     * Create a new "Filter" class implementing
     * CurrentNodeListFilter. Allocate registers for local
     * variables and local parameters passed in the closure to test().
     * Notice that local variables need to be "unboxed".
     */
    private void compileFilter(ClassGenerator classGen,
			       MethodGenerator methodGen) {
	TestGenerator testGen;
	LocalVariableGen local;
	FilterGenerator filterGen;

	_className = getXSLTC().getHelperClassName();
	filterGen = new FilterGenerator(_className,
					"java.lang.Object",
					toString(),
					ACC_PUBLIC | ACC_SUPER,
					new String[] {
					    CURRENT_NODE_LIST_FILTER
					},
					classGen.getStylesheet());

	final ConstantPoolGen cpg = filterGen.getConstantPool();
	final int length = (_closureVars == null) ? 0 : _closureVars.size();

	for (int i = 0; i < length; i++) {
	    VariableBase var = ((VariableRefBase) _closureVars.get(i)).getVariable();

	    filterGen.addField(new Field(ACC_PUBLIC,
					cpg.addUtf8(var.getEscapedName()),
					cpg.addUtf8(var.getType().toSignature()),
					null, cpg.getConstantPool()));
	}

	final InstructionList il = new InstructionList();
	testGen = new TestGenerator(ACC_PUBLIC | ACC_FINAL,
				    org.apache.bcel.generic.Type.BOOLEAN,
				    new org.apache.bcel.generic.Type[] {
					org.apache.bcel.generic.Type.INT,
					org.apache.bcel.generic.Type.INT,
					org.apache.bcel.generic.Type.INT,
					org.apache.bcel.generic.Type.INT,
					Util.getJCRefType(TRANSLET_SIG),
					Util.getJCRefType(NODE_ITERATOR_SIG)
				    },
				    new String[] {
					"node",
					"position",
					"last",
					"current",
					"translet",
					"iterator"
				    },
				    "test", _className, il, cpg);

	local = testGen.addLocalVariable("document",
					 Util.getJCRefType(DOM_INTF_SIG),
					 null, null);
	final String className = classGen.getClassName();
	il.append(filterGen.loadTranslet());
	il.append(new CHECKCAST(cpg.addClass(className)));
	il.append(new GETFIELD(cpg.addFieldref(className,
					       DOM_FIELD, DOM_INTF_SIG)));
	il.append(new ASTORE(local.getIndex()));

	testGen.setDomIndex(local.getIndex());

	_exp.translate(filterGen, testGen);
	il.append(IRETURN);

	testGen.stripAttributes(true);
	testGen.setMaxLocals();
	testGen.setMaxStack();
	testGen.removeNOPs();
	filterGen.addEmptyConstructor(ACC_PUBLIC);
	filterGen.addMethod(testGen.getMethod());

	getXSLTC().dumpClass(filterGen.getJavaClass());
    }

    /**
     * Returns true if the predicate is a test for the existance of an
     * element or attribute. All we have to do is to get the first node
     * from the step, check if it is there, and then return true/false.
     */
    public boolean isBooleanTest() {
	return (_exp instanceof BooleanExpr);
    }

    /**
     * Method to see if we can optimise the predicate by using a specialised
     * iterator for expressions like '/foo/bar[@attr = $var]', which are
     * very common in many stylesheets
     */
    public boolean isNodeValueTest() {
	if (!_canOptimize) return false;
	return (getStep() != null && getCompareValue() != null);
    }

   /**
     * Returns the step in an expression of the form 'step = value'.
     * Null is returned if the expression is not of the right form.
     * Optimization if off if null is returned.
     */
    public Step getStep() {
	if (_step != null) {
            return _step;
        }

	if (_exp == null) {
            return null;
        }

	if (_exp instanceof EqualityExpr) {
	    EqualityExpr exp = (EqualityExpr)_exp;
	    Expression left = exp.getLeft();
	    Expression right = exp.getRight();

	    if (left instanceof CastExpr) {
                left = ((CastExpr) left).getExpr();
            }
	    if (left instanceof Step) {
                _step = (Step) left;
            }

	    if (right instanceof CastExpr) {
                right = ((CastExpr)right).getExpr();
            }
	    if (right instanceof Step) {
                _step = (Step)right;
            }
	}
	return _step;
    }

    /**
     * Returns the value in an expression of the form 'step = value'.
     * A value may be either a literal string or a variable whose
     * type is string. Optimization if off if null is returned.
     */
    public Expression getCompareValue() {
	if (_value != null) {
            return _value;
        }

	if (_exp == null) {
            return null;
        }

	if (_exp instanceof EqualityExpr) {
	    EqualityExpr exp = (EqualityExpr) _exp;
	    Expression left = exp.getLeft();
	    Expression right = exp.getRight();

            if (left instanceof LiteralExpr) {
                _value = left;
                return _value;
            }
            if (left instanceof VariableRefBase &&
                left.getType() == Type.String)
            {
                _value = left;
                return _value;
            }

            if (right instanceof LiteralExpr) {
                _value = right;
                return _value;
            }
            if (right instanceof VariableRefBase &&
                right.getType() == Type.String)
            {
                _value = right;
                return _value;
            }
	}
	return null;
    }

    /**
     * Translate a predicate expression. This translation pushes
     * two references on the stack: a reference to a newly created
     * filter object and a reference to the predicate's closure.
     */
    public void translateFilter(ClassGenerator classGen,
				MethodGenerator methodGen)
    {
	final ConstantPoolGen cpg = classGen.getConstantPool();
	final InstructionList il = methodGen.getInstructionList();

	compileFilter(classGen, methodGen);

	il.append(new NEW(cpg.addClass(_className)));
	il.append(DUP);
	il.append(new INVOKESPECIAL(cpg.addMethodref(_className,
						     "<init>", "()V")));

	final int length = (_closureVars == null) ? 0 : _closureVars.size();

	for (int i = 0; i < length; i++) {
	    VariableRefBase varRef = (VariableRefBase) _closureVars.get(i);
	    VariableBase var = varRef.getVariable();
	    Type varType = var.getType();

	    il.append(DUP);

	    Closure variableClosure = _parentClosure;
	    while (variableClosure != null) {
		if (variableClosure.inInnerClass()) break;
		variableClosure = variableClosure.getParentClosure();
	    }

	    if (variableClosure != null) {
		il.append(ALOAD_0);
		il.append(new GETFIELD(
		    cpg.addFieldref(variableClosure.getInnerClassName(),
			var.getEscapedName(), varType.toSignature())));
	    }
	    else {
		il.append(var.loadInstruction());
	    }

	    il.append(new PUTFIELD(
		    cpg.addFieldref(_className, var.getEscapedName(),
			varType.toSignature())));
	}
    }

    /**
     * Translate a predicate expression. If non of the optimizations apply
     * then this translation pushes two references on the stack: a reference
     * to a newly created filter object and a reference to the predicate's
     * closure. See class Step for further details.
     */
    public void translate(ClassGenerator classGen, MethodGenerator methodGen) {

	final ConstantPoolGen cpg = classGen.getConstantPool();
	final InstructionList il = methodGen.getInstructionList();

	if (_nthPositionFilter || _nthDescendant) {
	    _exp.translate(classGen, methodGen);
	}
	else if (isNodeValueTest() && (getParent() instanceof Step)) {
	    _value.translate(classGen, methodGen);
	    il.append(new CHECKCAST(cpg.addClass(STRING_CLASS)));
	    il.append(new PUSH(cpg, ((EqualityExpr)_exp).getOp()));
	}
	else {
	    translateFilter(classGen, methodGen);

FUNC_DECL IF IF IF VAR_DECL PARAM BINARY_OP BINARY_OP BINARY_OP RETURN IF RETURN IF VAR_DECL VAR_DECL IF IF CALL ASSIGN BINARY_OP ASSIGN BINARY_OP ASSIGN CALL CALL ASSIGN BINARY_OP ASSIGN ASSIGN ASSIGN ASSIGN LITERAL_ANCHOR BINARY_OP CALL CALL CALL CALL CALL ASSIGN IF LITERAL_ANCHOR BINARY_OP CALL CALL CALL CALL ASSIGN ASSIGN CALL LITERAL_ANCHOR ASSIGN ASSIGN ASSIGN CALL CALL LITERAL_ANCHOR RETURN VAR_DECL BINARY_OP CALL ASSIGN ASSIGN CALL CALL BINARY_OP CALL BINARY_OP BINARY_OP CALL

FUNC_DECL IF VAR_DECL VAR_DECL PARAM PARAM IF BINARY_OP BINARY_OP CALL CALL BINARY_OP CALL CALL CALL CALL CALL CALL CALL CALL CALL CALL


org.apache.xml.utils.AttList

public class AttList implements Attributes
{

  /** List of attribute nodes          */
  NamedNodeMap m_attrs;

  /** Index of last attribute node          */
  int m_lastIndex;

  /** Local reference to DOMHelper          */
  DOMHelper m_dh;

  /**
   * Constructor AttList
   *
   *
   * @param attrs List of attributes this will contain
   * @param dh DOMHelper
   */
  public AttList(NamedNodeMap attrs, DOMHelper dh)
  {

    m_attrs = attrs;
    m_lastIndex = m_attrs.getLength() - 1;
    m_dh = dh;
  }

  /**
   * Get the number of attribute nodes in the list
   *
   *
   * @return number of attribute nodes
   */
  public int getLength()
  {
    return m_attrs.getLength();
  }

  /**
   * Look up an attribute's Namespace URI by index.
   *
   * @param index The attribute index (zero-based).
   * @return The Namespace URI, or the empty string if none
   *         is available, or null if the index is out of
   *         range.
   */
  public String getURI(int index)
  {
    String ns = m_dh.getNamespaceOfNode(((Attr) m_attrs.item(index)));
    if(null == ns)
      ns = "";
    return ns;
  }

  /**
   * Look up an attribute's local name by index.
   *
   * @param index The attribute index (zero-based).
   * @return The local name, or the empty string if Namespace
   *         processing is not being performed, or null
   *         if the index is out of range.
   */
  public String getLocalName(int index)
  {
    return m_dh.getLocalNameOfNode(((Attr) m_attrs.item(index)));
  }

  /**
   * Look up an attribute's qualified name by index.
   *
   *
   * @param index The attribute index (zero-based).
   *
   * @return The attribute's qualified name
   */
  public String getQName(int i)
  {
    return ((Attr) m_attrs.item(i)).getName();
  }

  /**
   * Get the attribute's node type by index
   *
   *
   * @param index The attribute index (zero-based)
   *
   * @return the attribute's node type
   */
  public String getType(int i)
  {
  }

  /**
   * Get the attribute's node value by index
   *
   *
   * @param index The attribute index (zero-based)
   *
   * @return the attribute's node value
   */
  public String getValue(int i)
  {
    return ((Attr) m_attrs.item(i)).getValue();
  }

  /**
   * Get the attribute's node type by name
   *
   *
   * @param name Attribute name
   *
   * @return the attribute's node type
   */
  public String getType(String name)
  {
  }

  /**
   * Look up an attribute's type by Namespace name.
   *
   * @param uri The Namespace URI, or the empty String if the
   *        name has no Namespace URI.
   * @param localName The local name of the attribute.
   * @return The attribute type as a string, or null if the
   *         attribute is not in the list or if Namespace
   *         processing is not being performed.
   */
  public String getType(String uri, String localName)
  {
  }

  /**
   * Look up an attribute's value by name.
   *
   *
   * @param name The attribute node's name
   *
   * @return The attribute node's value
   */
  public String getValue(String name)
  {
    Attr attr = ((Attr) m_attrs.getNamedItem(name));
    return (null != attr)
          ? attr.getValue() : null;
  }

  /**
   * Look up an attribute's value by Namespace name.
   *
   * @param uri The Namespace URI, or the empty String if the
   *        name has no Namespace URI.
   * @param localName The local name of the attribute.
   * @return The attribute value as a string, or null if the
   *         attribute is not in the list.
   */
  public String getValue(String uri, String localName)
  {
		Node a=m_attrs.getNamedItemNS(uri,localName);
		return (a==null) ? null : a.getNodeValue();
  }

  /**
   * Look up the index of an attribute by Namespace name.
   *
   * @param uri The Namespace URI, or the empty string if
   *        the name has no Namespace URI.
   * @param localPart The attribute's local name.
   * @return The index of the attribute, or -1 if it does not
   *         appear in the list.
   */
  public int getIndex(String uri, String localPart)
  {
    for(int i=m_attrs.getLength()-1;i>=0;--i)
    {
      Node a=m_attrs.item(i);
      String u=a.getNamespaceURI();
      if( (u==null ? uri==null : u.equals(uri))
	  &&
	  a.getLocalName().equals(localPart) )
	return i;
    }
    return -1;
  }

  /**
   * Look up the index of an attribute by raw XML 1.0 name.
   *
   * @param qName The qualified (prefixed) name.
   * @return The index of the attribute, or -1 if it does not
   *         appear in the list.
   */
  public int getIndex(String qName)
  {
    for(int i=m_attrs.getLength()-1;i>=0;--i)
    {
      Node a=m_attrs.item(i);
      if(a.getNodeName().equals(qName) )
	return i;
    }
    return -1;

FUNC_DECL PARAM PARAM ASSIGN ASSIGN ASSIGN BINARY_OP LITERAL_ANCHOR CALL