Bug 223318: Allow alphabetical sorting of package fragment roots

- PreferenceConstants: Added named preference
'org.eclipse.jdt.ui.sortLibraryEntriesByName'
- AppearancePreferencePage: Added check box for new preference
- PreferenceMessage: Added label text for new preference check box
- JavaElementComparator:
  - new constructor that takes the preference value
  - Skip classpath index comparison when alphabetical sorting is
activated
- PackageExplorerPart: Recreate JavaElementComparator on preference
change and refresh the tree

Change-Id: Ia208f940e560d86e7552480b81c4cf401176e54d
Signed-off-by: Karsten Thoms <karsten.thoms@itemis.de>
diff --git a/org.eclipse.jdt.ui/ui/org/eclipse/jdt/internal/ui/packageview/PackageExplorerPart.java b/org.eclipse.jdt.ui/ui/org/eclipse/jdt/internal/ui/packageview/PackageExplorerPart.java
index d20adf7..05c4d50 100644
--- a/org.eclipse.jdt.ui/ui/org/eclipse/jdt/internal/ui/packageview/PackageExplorerPart.java
+++ b/org.eclipse.jdt.ui/ui/org/eclipse/jdt/internal/ui/packageview/PackageExplorerPart.java
@@ -1,5 +1,5 @@
 /*******************************************************************************
- * Copyright (c) 2000, 2016 IBM Corporation and others.
+ * Copyright (c) 2000, 2018 IBM Corporation and others.
  * All rights reserved. This program and the accompanying materials
  * are made available under the terms of the Eclipse Public License v1.0
  * which accompanies this distribution, and is available at
@@ -7,7 +7,8 @@
  *
  * Contributors:
  *     IBM Corporation - initial API and implementation
- *******************************************************************************/
+ *     Karsten Thoms (itemis) - Bug#223318
+******************************************************************************/
 package org.eclipse.jdt.internal.ui.packageview;
 
 import java.io.IOException;
@@ -1208,6 +1209,10 @@
 			refreshViewer= true;
 		} else if (MembersOrderPreferenceCache.isMemberOrderProperty(event.getProperty())) {
 			refreshViewer= true;
+		} else if (PreferenceConstants.APPEARANCE_SORT_LIBRARY_ENTRIES_BY_NAME.equals(event.getProperty())) {
+			// set new comparator, since it might evaluate this property on construction
+			setComparator();
+			refreshViewer = true;
 		}
 
 		if (refreshViewer)
@@ -1496,7 +1501,8 @@
 		if (getRootMode() == WORKING_SETS_AS_ROOTS) {
 			fViewer.setComparator(new WorkingSetAwareJavaElementSorter());
 		} else {
-			fViewer.setComparator(new JavaElementComparator());
+			boolean sortLibraryEntriesByName = JavaPlugin.getDefault().getPreferenceStore().getBoolean(PreferenceConstants.APPEARANCE_SORT_LIBRARY_ENTRIES_BY_NAME);
+			fViewer.setComparator(new JavaElementComparator(sortLibraryEntriesByName));
 		}
 	}
 }
diff --git a/org.eclipse.jdt.ui/ui/org/eclipse/jdt/internal/ui/preferences/AppearancePreferencePage.java b/org.eclipse.jdt.ui/ui/org/eclipse/jdt/internal/ui/preferences/AppearancePreferencePage.java
index 5928047..0f00233 100644
--- a/org.eclipse.jdt.ui/ui/org/eclipse/jdt/internal/ui/preferences/AppearancePreferencePage.java
+++ b/org.eclipse.jdt.ui/ui/org/eclipse/jdt/internal/ui/preferences/AppearancePreferencePage.java
@@ -57,6 +57,7 @@
 	private static final String PREF_PKG_NAME_ABBREVIATION_PATTERN_FOR_PKG_VIEW= PreferenceConstants.APPEARANCE_PKG_NAME_ABBREVIATION_PATTERN_FOR_PKG_VIEW;
 	private static final String STACK_BROWSING_VIEWS_VERTICALLY= PreferenceConstants.BROWSING_STACK_VERTICALLY;
 	private static final String PREF_FOLD_PACKAGES_IN_PACKAGE_EXPLORER= PreferenceConstants.APPEARANCE_FOLD_PACKAGES_IN_PACKAGE_EXPLORER;
+	private static final String PREF_SORT_LIBRARY_ENTRIES_BY_NAME= PreferenceConstants.APPEARANCE_SORT_LIBRARY_ENTRIES_BY_NAME;
 	private static final String PREF_CATEGORY= PreferenceConstants.APPEARANCE_CATEGORY;
 	private static final String DECORATE_TEST_CODE_CONTAINER_ICONS= PreferenceConstants.DECORATE_TEST_CODE_CONTAINER_ICONS;
 
@@ -71,6 +72,7 @@
 	private StringDialogField fAbbreviatePackageNamePattern;
 	private SelectionButtonDialogField fFoldPackagesInPackageExplorer;
 	private SelectionButtonDialogField fShowMethodTypeParameters;
+	private SelectionButtonDialogField fSortLibraryEntriesByName;
 
 	public AppearancePreferencePage() {
 		setPreferenceStore(JavaPlugin.getDefault().getPreferenceStore());
@@ -111,6 +113,10 @@
 		fFoldPackagesInPackageExplorer.setDialogFieldListener(listener);
 		fFoldPackagesInPackageExplorer.setLabelText(PreferencesMessages.AppearancePreferencePage_foldEmptyPackages);
 
+		fSortLibraryEntriesByName= new SelectionButtonDialogField(SWT.CHECK);
+		fSortLibraryEntriesByName.setDialogFieldListener(listener);
+		fSortLibraryEntriesByName.setLabelText(PreferencesMessages.AppearancePreferencePage_sortLibraryEntries);
+
 		fCompressPackageNames= new SelectionButtonDialogField(SWT.CHECK);
 		fCompressPackageNames.setDialogFieldListener(listener);
 		fCompressPackageNames.setLabelText(PreferencesMessages.AppearancePreferencePage_pkgNamePatternEnable_label);
@@ -144,6 +150,7 @@
 		doDialogFieldChanged(fAbbreviatePackageNames);
 		fAbbreviatePackageNamePattern.setEnabled(fAbbreviatePackageNames.isSelected());
 		fFoldPackagesInPackageExplorer.setSelection(prefs.getBoolean(PREF_FOLD_PACKAGES_IN_PACKAGE_EXPLORER));
+		fSortLibraryEntriesByName.setSelection(prefs.getBoolean(PREF_SORT_LIBRARY_ENTRIES_BY_NAME));
 	}
 
 	/*
@@ -178,6 +185,7 @@
 		fShowMembersInPackageView.doFillIntoGrid(result, nColumns);
 		fFoldPackagesInPackageExplorer.doFillIntoGrid(result, nColumns);
 		fDecorateTestCodeContainerIcons.doFillIntoGrid(result, nColumns);
+		fSortLibraryEntriesByName.doFillIntoGrid(result, nColumns);
 
 		new Separator().doFillIntoGrid(result, nColumns);
 
@@ -268,6 +276,7 @@
 		prefs.setValue(PREF_PKG_NAME_ABBREVIATION_PATTERN_FOR_PKG_VIEW, fAbbreviatePackageNamePattern.getText());
 		prefs.setValue(PREF_ABBREVIATE_PACKAGE_NAMES, fAbbreviatePackageNames.isSelected());
 		prefs.setValue(PREF_FOLD_PACKAGES_IN_PACKAGE_EXPLORER, fFoldPackagesInPackageExplorer.isSelected());
+		prefs.setValue(PREF_SORT_LIBRARY_ENTRIES_BY_NAME, fSortLibraryEntriesByName.isSelected());
 		JavaPlugin.flushInstanceScope();
 		return super.performOk();
 	}
@@ -289,6 +298,7 @@
 		fAbbreviatePackageNamePattern.setText(prefs.getDefaultString(PREF_PKG_NAME_ABBREVIATION_PATTERN_FOR_PKG_VIEW));
 		fAbbreviatePackageNames.setSelection(prefs.getDefaultBoolean(PREF_ABBREVIATE_PACKAGE_NAMES));
 		fFoldPackagesInPackageExplorer.setSelection(prefs.getDefaultBoolean(PREF_FOLD_PACKAGES_IN_PACKAGE_EXPLORER));
+		fSortLibraryEntriesByName.setSelection(prefs.getDefaultBoolean(PREF_SORT_LIBRARY_ENTRIES_BY_NAME));
 		super.performDefaults();
 	}
 }
diff --git a/org.eclipse.jdt.ui/ui/org/eclipse/jdt/internal/ui/preferences/PreferencesMessages.java b/org.eclipse.jdt.ui/ui/org/eclipse/jdt/internal/ui/preferences/PreferencesMessages.java
index c42cbeb..79e9d4d 100644
--- a/org.eclipse.jdt.ui/ui/org/eclipse/jdt/internal/ui/preferences/PreferencesMessages.java
+++ b/org.eclipse.jdt.ui/ui/org/eclipse/jdt/internal/ui/preferences/PreferencesMessages.java
@@ -311,6 +311,7 @@
 	public static String AppearancePreferencePage_packageNameCompressionPattern_error_isEmpty;
 	public static String AppearancePreferencePage_packageNameAbbreviationPattern_error_isInvalid;
 	public static String AppearancePreferencePage_foldEmptyPackages;
+	public static String AppearancePreferencePage_sortLibraryEntries;
 	public static String CodeFormatterPreferencePage_title;
 	public static String SourceAttachmentPropertyPage_not_supported;
 	public static String SourceAttachmentPropertyPage_read_only;
diff --git a/org.eclipse.jdt.ui/ui/org/eclipse/jdt/internal/ui/preferences/PreferencesMessages.properties b/org.eclipse.jdt.ui/ui/org/eclipse/jdt/internal/ui/preferences/PreferencesMessages.properties
index ef80437..93fa277 100644
--- a/org.eclipse.jdt.ui/ui/org/eclipse/jdt/internal/ui/preferences/PreferencesMessages.properties
+++ b/org.eclipse.jdt.ui/ui/org/eclipse/jdt/internal/ui/preferences/PreferencesMessages.properties
@@ -310,7 +310,8 @@
 AppearancePreferencePage_preferenceOnlyEffectiveForNewPerspectives=This preference will only take effect on new perspectives
 AppearancePreferencePage_packageNameCompressionPattern_error_isEmpty=Enter a package name compression pattern
 AppearancePreferencePage_packageNameAbbreviationPattern_error_isInvalid=Enter a valid package name abbreviation pattern
-AppearancePreferencePage_foldEmptyPackages= &Fold empty packages in hierarchical layout in Package and Project Explorer
+AppearancePreferencePage_foldEmptyPackages=&Fold empty packages in hierarchical layout in Package and Project Explorer
+AppearancePreferencePage_sortLibraryEntries=Sort library entries &alphabetically in Package Explorer
 
 CodeFormatterPreferencePage_title=Code Formatter
 
diff --git a/org.eclipse.jdt.ui/ui/org/eclipse/jdt/ui/JavaElementComparator.java b/org.eclipse.jdt.ui/ui/org/eclipse/jdt/ui/JavaElementComparator.java
index 99855dc..cfd36cb 100644
--- a/org.eclipse.jdt.ui/ui/org/eclipse/jdt/ui/JavaElementComparator.java
+++ b/org.eclipse.jdt.ui/ui/org/eclipse/jdt/ui/JavaElementComparator.java
@@ -1,5 +1,5 @@
 /*******************************************************************************
- * Copyright (c) 2000, 2011 IBM Corporation and others.
+ * Copyright (c) 2000, 2018 IBM Corporation and others.
  * All rights reserved. This program and the accompanying materials
  * are made available under the terms of the Eclipse Public License v1.0
  * which accompanies this distribution, and is available at
@@ -7,6 +7,7 @@
  *
  * Contributors:
  *     IBM Corporation - initial API and implementation
+ *     Karsten Thoms (itemis) - Bug#223318
  *******************************************************************************/
 package org.eclipse.jdt.ui;
 
@@ -51,7 +52,7 @@
 
 /**
  * Viewer comparator for Java elements. Ordered by element category, then by element name.
- * Package fragment roots are sorted as ordered on the classpath.
+ * Package fragment roots are either sorted as ordered on the classpath, or by their name.
  *
  * <p>
  * This class may be instantiated; it is not intended to be subclassed.
@@ -84,16 +85,30 @@
 	private static final int JAVAELEMENTS= 50;
 	private static final int OTHERS= 51;
 
-	private MembersOrderPreferenceCache fMemberOrderCache;
+	private final MembersOrderPreferenceCache fMemberOrderCache;
+	private final boolean fSortPFRByName;
 
 	/**
 	 * Constructor.
 	 */
 	public JavaElementComparator() {
+		this(false);
+	}
+	
+	/**
+	 * Constructor.
+	 * 
+	 * @param sortPFRByName When <code>true</code> {@link IPackageFragmentRoot}s are sorted by name and not by their classpath order
+	 * 
+	 * @since 3.14
+	 */
+	public JavaElementComparator(boolean sortPFRByName) {
 		super(null); // delay initialization of collator
 		fMemberOrderCache= JavaPlugin.getDefault().getMemberOrderPreferenceCache();
+		fSortPFRByName = sortPFRByName;
 	}
 
+		
 	@Override
 	public int category(Object element) {
 		if (element instanceof IJavaElement) {
@@ -184,7 +199,7 @@
 		int cat1= category(e1);
 		int cat2= category(e2);
 
-		if (needsClasspathComparision(e1, cat1, e2, cat2)) {
+		if (needsClasspathComparison(e1, cat1, e2, cat2)) {
 			IPackageFragmentRoot root1= getPackageFragmentRoot(e1);
 			IPackageFragmentRoot root2= getPackageFragmentRoot(e2);
 			if (root1 == null) {
@@ -323,7 +338,7 @@
 		return Integer.MAX_VALUE;
 	}
 
-	private boolean needsClasspathComparision(Object e1, int cat1, Object e2, int cat2) {
+	private boolean needsClasspathComparison(Object e1, int cat1, Object e2, int cat2) {
 		if ((cat1 == PACKAGEFRAGMENTROOTS && cat2 == PACKAGEFRAGMENTROOTS) ||
 			(cat1 == PACKAGEFRAGMENT &&
 				((IPackageFragment)e1).getParent().getResource() instanceof IProject &&
@@ -331,6 +346,11 @@
 			(cat1 == PACKAGEFRAGMENTROOTS &&
 				cat2 == PACKAGEFRAGMENT &&
 				((IPackageFragment)e2).getParent().getResource() instanceof IProject)) {
+			// when PFRs should be sorted by name, they do not need classpath comparison
+			if (fSortPFRByName && cat1 == PACKAGEFRAGMENTROOTS && cat2 == PACKAGEFRAGMENTROOTS) {
+				// categories might be PACKAGEFRAGMENTROOTS, but not necessarily compared objects are PFR instances
+				return (e1 instanceof IPackageFragmentRoot && e2 instanceof IPackageFragmentRoot) ? false : true;
+			}
 			IJavaProject p1= getJavaProject(e1);
 			return p1 != null && p1.equals(getJavaProject(e2));
 		}
diff --git a/org.eclipse.jdt.ui/ui/org/eclipse/jdt/ui/PreferenceConstants.java b/org.eclipse.jdt.ui/ui/org/eclipse/jdt/ui/PreferenceConstants.java
index ff4665d..69596e2 100644
--- a/org.eclipse.jdt.ui/ui/org/eclipse/jdt/ui/PreferenceConstants.java
+++ b/org.eclipse.jdt.ui/ui/org/eclipse/jdt/ui/PreferenceConstants.java
@@ -170,6 +170,16 @@
 	public static final String APPEARANCE_FOLD_PACKAGES_IN_PACKAGE_EXPLORER= "org.eclipse.jdt.ui.flatPackagesInPackageExplorer";//$NON-NLS-1$
 
 	/**
+	 * A named preference that controls if entries of library containers are sorted alphabetically.
+	 * <p>
+	 * Value is of type <code>Boolean</code>: if <code>true</code> entries are sorted alphabetically, otherwise
+	 * by their occurance order.
+	 * </p>
+	 * @since 3.14
+	 */
+	public static final String APPEARANCE_SORT_LIBRARY_ENTRIES_BY_NAME= "org.eclipse.jdt.ui.sortLibraryEntriesByName";//$NON-NLS-1$
+
+	/**
 	 * A named preference that defines how member elements are ordered by the
 	 * Java views using the <code>JavaElementSorter</code>.
 	 * <p>