1 /******************************************************************************* 2 * Copyright (c) 2010, 2019 IBM Corporation and others. 3 * 4 * This program and the accompanying materials 5 * are made available under the terms of the Eclipse Public License 2.0 6 * which accompanies this distribution, and is available at 7 * https://www.eclipse.org/legal/epl-2.0/ 8 * 9 * SPDX-License-Identifier: EPL-2.0 10 * 11 * Contributors: 12 * IBM Corporation - initial API and implementation 13 *******************************************************************************/ 14 package org.eclipse.pde.api.tools.ui.internal.preferences; 15 16 import java.io.File; 17 import java.util.ArrayList; 18 import java.util.HashMap; 19 import java.util.HashSet; 20 import java.util.Iterator; 21 22 import org.eclipse.core.resources.IProject; 23 import org.eclipse.core.resources.IResource; 24 import org.eclipse.core.runtime.CoreException; 25 import org.eclipse.core.runtime.preferences.IEclipsePreferences; 26 import org.eclipse.core.runtime.preferences.InstanceScope; 27 import org.eclipse.jface.dialogs.Dialog; 28 import org.eclipse.jface.dialogs.MessageDialog; 29 import org.eclipse.jface.preference.PreferencePage; 30 import org.eclipse.jface.resource.ImageDescriptor; 31 import org.eclipse.jface.viewers.ArrayContentProvider; 32 import org.eclipse.jface.viewers.CheckboxTableViewer; 33 import org.eclipse.jface.viewers.ColumnLabelProvider; 34 import org.eclipse.jface.viewers.IStructuredSelection; 35 import org.eclipse.jface.viewers.StructuredSelection; 36 import org.eclipse.osgi.util.NLS; 37 import org.eclipse.pde.api.tools.internal.IApiCoreConstants; 38 import org.eclipse.pde.api.tools.internal.provisional.ApiPlugin; 39 import org.eclipse.pde.api.tools.internal.provisional.IApiMarkerConstants; 40 import org.eclipse.pde.api.tools.internal.search.UseScanManager; 41 import org.eclipse.pde.api.tools.internal.util.Util; 42 import org.eclipse.pde.api.tools.ui.internal.ApiUIPlugin; 43 import org.eclipse.pde.api.tools.ui.internal.IApiToolsHelpContextIds; 44 import org.eclipse.pde.api.tools.ui.internal.SWTFactory; 45 import org.eclipse.swt.SWT; 46 import org.eclipse.swt.events.KeyListener; 47 import org.eclipse.swt.events.SelectionListener; 48 import org.eclipse.swt.graphics.Image; 49 import org.eclipse.swt.layout.GridData; 50 import org.eclipse.swt.widgets.Button; 51 import org.eclipse.swt.widgets.Composite; 52 import org.eclipse.swt.widgets.Control; 53 import org.eclipse.swt.widgets.DirectoryDialog; 54 import org.eclipse.swt.widgets.FileDialog; 55 import org.eclipse.swt.widgets.Table; 56 import org.eclipse.ui.IWorkbench; 57 import org.eclipse.ui.IWorkbenchPreferencePage; 58 import org.eclipse.ui.PlatformUI; 59 import org.eclipse.ui.dialogs.PreferenceLinkArea; 60 import org.eclipse.ui.preferences.IWorkbenchPreferenceContainer; 61 import org.eclipse.ui.preferences.IWorkingCopyManager; 62 import org.eclipse.ui.preferences.WorkingCopyManager; 63 import org.osgi.service.prefs.BackingStoreException; 64 65 /** 66 * Preference page to allow users to add use scans. The use scans are analyzed 67 * in the API Tools builder to see if any methods found in the scan have been 68 * removed. 69 * 70 * @since 3.7 71 */ 72 public class ApiUseScanPreferencePage extends PreferencePage implements IWorkbenchPreferencePage { 73 74 public static final String ID = "org.eclipse.pde.api.tools.ui.apiusescan.prefpage"; //$NON-NLS-1$ 75 76 private IWorkingCopyManager fManager; 77 CheckboxTableViewer fTableViewer; 78 HashSet<String> fLocationList = new HashSet<>(); 79 Button remove = null; 80 Button editbutton = null; 81 82 /** 83 * Column provider for the use scan table 84 */ 85 class TableColumnLabelProvider extends ColumnLabelProvider { 86 87 Image archive = null; 88 89 @Override dispose()90 public void dispose() { 91 if (archive != null) { 92 archive.dispose(); 93 } 94 super.dispose(); 95 } 96 97 @Override getImage(Object element)98 public Image getImage(Object element) { 99 File file = new File(element.toString()); 100 if (file.isDirectory()) { 101 return PlatformUI.getWorkbench().getSharedImages().getImage(org.eclipse.ui.ISharedImages.IMG_OBJ_FOLDER); 102 } 103 if (archive == null) { 104 ImageDescriptor image = PlatformUI.getWorkbench().getEditorRegistry().getImageDescriptor(file.getName()); 105 archive = image.createImage(); 106 } 107 return archive; 108 } 109 } 110 111 @Override init(IWorkbench workbench)112 public void init(IWorkbench workbench) { 113 } 114 115 @Override createContents(Composite parent)116 protected Control createContents(Composite parent) { 117 Composite comp = SWTFactory.createComposite(parent, 2, 1, GridData.FILL_HORIZONTAL, 0, 0); 118 119 SWTFactory.createWrapLabel(comp, PreferenceMessages.ApiUseScanPreferencePage_0, 2, 250); 120 SWTFactory.createVerticalSpacer(comp, 1); 121 SWTFactory.createWrapLabel(comp, PreferenceMessages.ApiUseScanPreferencePage_2, 2); 122 123 Table table = new Table(comp, SWT.FULL_SELECTION | SWT.MULTI | SWT.BORDER | SWT.CHECK | SWT.V_SCROLL); 124 table.setLayoutData(new GridData(GridData.FILL_BOTH)); 125 GridData gd = (GridData) table.getLayoutData(); 126 gd.widthHint = 250; 127 table.addKeyListener(KeyListener.keyReleasedAdapter(e -> { 128 if (e.stateMask == SWT.NONE && e.keyCode == SWT.DEL) { 129 removeLocation(); 130 } 131 })); 132 fTableViewer = new CheckboxTableViewer(table); 133 fTableViewer.setLabelProvider(new TableColumnLabelProvider()); 134 fTableViewer.setContentProvider(ArrayContentProvider.getInstance()); 135 136 Composite bcomp = SWTFactory.createComposite(comp, 1, 1, GridData.FILL_VERTICAL | GridData.VERTICAL_ALIGN_BEGINNING, 0, 0); 137 Button button = SWTFactory.createPushButton(bcomp, PreferenceMessages.ApiUseScanPreferencePage_3, null); 138 button.addSelectionListener(SelectionListener.widgetSelectedAdapter(e -> select(true))); 139 button = SWTFactory.createPushButton(bcomp, PreferenceMessages.ApiUseScanPreferencePage_10, null); 140 button.addSelectionListener(SelectionListener.widgetSelectedAdapter(e -> select(false))); 141 142 SWTFactory.createHorizontalSpacer(bcomp, 1); 143 144 button = SWTFactory.createPushButton(bcomp, PreferenceMessages.ApiUseScanPreferencePage_4, null); 145 button.addSelectionListener(SelectionListener.widgetSelectedAdapter(e -> { 146 String loc = getDirectory(null); 147 if (loc != null) { 148 String exactLocation = UseScanManager.getExactScanLocation(loc); 149 if (exactLocation == null) { 150 addLocation(loc); 151 } else { 152 addLocation(exactLocation); 153 } 154 } 155 })); 156 button = SWTFactory.createPushButton(bcomp, PreferenceMessages.ApiUseScanPreferencePage_5, null); 157 button.addSelectionListener(SelectionListener.widgetSelectedAdapter(e -> { 158 String loc = getArchive(null); 159 if (loc != null) { 160 addLocation(loc); 161 } 162 })); 163 164 editbutton = SWTFactory.createPushButton(bcomp, PreferenceMessages.ApiUseScanPreferencePage_1, null); 165 editbutton.setEnabled(false); 166 editbutton.addSelectionListener(SelectionListener.widgetSelectedAdapter(e -> edit())); 167 remove = SWTFactory.createPushButton(bcomp, PreferenceMessages.ApiUseScanPreferencePage_6, null); 168 remove.setEnabled(false); 169 remove.addSelectionListener(SelectionListener.widgetSelectedAdapter(e -> removeLocation())); 170 171 fTableViewer.addSelectionChangedListener(event -> { 172 IStructuredSelection selection = fTableViewer.getStructuredSelection(); 173 remove.setEnabled(!selection.isEmpty()); 174 editbutton.setEnabled(selection.size() == 1); 175 }); 176 fTableViewer.addDoubleClickListener(event -> edit()); 177 178 HashMap<String, Object> linkdata = new HashMap<>(); 179 linkdata.put(ApiErrorsWarningsPreferencePage.INITIAL_TAB, Integer.valueOf(ApiErrorsWarningsConfigurationBlock.API_USE_SCANS_PAGE_ID)); 180 PreferenceLinkArea apiErrorLinkArea = new PreferenceLinkArea(comp, SWT.NONE, ApiErrorsWarningsPreferencePage.ID, PreferenceMessages.ApiUseScanPreferencePage_9, (IWorkbenchPreferenceContainer) getContainer(), linkdata); 181 GridData data = new GridData(GridData.FILL_HORIZONTAL | GridData.GRAB_HORIZONTAL); 182 data.widthHint = 250; 183 data.horizontalSpan = 2; 184 apiErrorLinkArea.getControl().setLayoutData(data); 185 186 PlatformUI.getWorkbench().getHelpSystem().setHelp(parent, IApiToolsHelpContextIds.APIUSESCANS_PREF_PAGE); 187 performInit(); 188 validateScans(); 189 Dialog.applyDialogFont(comp); 190 return comp; 191 } 192 193 /** 194 * Selects (checks) all of the entries in the table 195 * 196 * @param checked 197 */ select(boolean checked)198 void select(boolean checked) { 199 fTableViewer.setAllChecked(checked); 200 fTableViewer.refresh(); 201 } 202 203 /** 204 * Allows users to select a directory with a use scan in it 205 * 206 * @param prevLocation 207 * @return the new directory or <code>null</code> if the dialog was 208 * cancelled 209 */ getDirectory(String prevLocation)210 String getDirectory(String prevLocation) { 211 DirectoryDialog dialog = new DirectoryDialog(getShell()); 212 dialog.setMessage(PreferenceMessages.ApiUseScanPreferencePage_7); 213 if (prevLocation != null) { 214 dialog.setFilterPath(prevLocation); 215 } 216 return dialog.open(); 217 } 218 219 /** 220 * Allows the user to select an archive from the file system 221 * 222 * @param file a starting file 223 * @return the path to the new archive or <code>null</code> if cancelled 224 */ getArchive(File file)225 String getArchive(File file) { 226 FileDialog dialog = new FileDialog(getShell(), SWT.OPEN); 227 dialog.setFilterNames(new String[] { PreferenceMessages.archives__zip }); 228 dialog.setFilterExtensions(new String[] { "*.zip;*.jar" }); //$NON-NLS-1$ 229 if (file != null) { 230 dialog.setFilterPath(file.getParent()); 231 dialog.setFileName(file.getName()); 232 } 233 return dialog.open(); 234 } 235 236 /** 237 * Adds the given location to the table 238 * 239 * @param location 240 */ addLocation(String location)241 void addLocation(String location) { 242 fLocationList.add(location); 243 fTableViewer.refresh(); 244 fTableViewer.setChecked(location, true); 245 fTableViewer.setSelection(new StructuredSelection(location)); 246 // do the whole pass in case you have more than one invalid location 247 validateScans(); 248 } 249 250 /** 251 * Allows you to edit the location of the scan 252 */ edit()253 void edit() { 254 IStructuredSelection selection = fTableViewer.getStructuredSelection(); 255 String location = selection.getFirstElement().toString(); 256 File file = new File(location); 257 String newloc = null; 258 if (file.isDirectory()) { 259 newloc = getDirectory(location); 260 } else { 261 newloc = getArchive(file); 262 } 263 if (newloc != null) { 264 fLocationList.remove(location); 265 addLocation(newloc); 266 } 267 } 268 269 /** 270 * Removes the selected locations 271 */ removeLocation()272 void removeLocation() { 273 IStructuredSelection selection = fTableViewer.getStructuredSelection(); 274 fLocationList.removeAll(selection.toList()); 275 fTableViewer.refresh(); 276 validateScans(); 277 } 278 279 /** 280 * Validates that the scan are all still valid 281 */ validateScans()282 private void validateScans() { 283 if (fLocationList.size() > 0) { 284 String loc = null; 285 for (Iterator<String> iterator = fLocationList.iterator(); iterator.hasNext();) { 286 loc = iterator.next(); 287 if (!UseScanManager.isValidScanLocation(loc)) { 288 setErrorMessage(NLS.bind(PreferenceMessages.ApiUseScanPreferencePage_8, loc)); 289 setValid(false); 290 return; 291 } 292 } 293 } 294 setValid(true); 295 setErrorMessage(null); 296 } 297 298 @Override performOk()299 public boolean performOk() { 300 applyChanges(); 301 return true; 302 } 303 304 @Override performApply()305 protected void performApply() { 306 applyChanges(); 307 } 308 309 @Override performDefaults()310 protected void performDefaults() { 311 fLocationList.clear(); 312 fTableViewer.refresh(); 313 setValid(true); 314 setErrorMessage(null); 315 super.performDefaults(); 316 } 317 318 /** 319 * Initializes the page 320 */ performInit()321 private void performInit() { 322 if (getContainer() == null) { 323 fManager = new WorkingCopyManager(); 324 } else { 325 fManager = ((IWorkbenchPreferenceContainer) getContainer()).getWorkingCopyManager(); 326 } 327 328 fLocationList.clear(); 329 330 String location = getStoredValue(IApiCoreConstants.API_USE_SCAN_LOCATION, null); 331 332 ArrayList<String> checkedLocations = new ArrayList<>(); 333 if (location != null && location.length() > 0) { 334 String[] locations = location.split(UseScanManager.ESCAPE_REGEX + UseScanManager.LOCATION_DELIM); 335 for (String locationString : locations) { 336 String values[] = locationString.split(UseScanManager.ESCAPE_REGEX + UseScanManager.STATE_DELIM); 337 fLocationList.add(values[0]); 338 if (Boolean.parseBoolean(values[1])) { 339 checkedLocations.add(values[0]); 340 } 341 } 342 fLocationList.remove(""); //$NON-NLS-1$ 343 } 344 fTableViewer.setInput(fLocationList); 345 fTableViewer.setCheckedElements(checkedLocations.toArray(new String[checkedLocations.size()])); 346 fTableViewer.refresh(); 347 348 setErrorMessage(null); 349 } 350 351 /** 352 * Save changes to the preferences 353 */ applyChanges()354 private void applyChanges() { 355 StringBuilder locations = new StringBuilder(); 356 for (String string : fLocationList) { 357 Object location = string; 358 locations.append(location); 359 locations.append(UseScanManager.STATE_DELIM); 360 locations.append(fTableViewer.getChecked(location)); 361 locations.append(UseScanManager.LOCATION_DELIM); 362 } 363 364 Object[] newCheckedLocations = fTableViewer.getCheckedElements(); 365 boolean hasLocationChanges = hasLocationsChanges(locations.toString()); 366 if (hasLocationChanges && newCheckedLocations.length != 0) { 367 IProject[] projects = Util.getApiProjects(); 368 // If there are API projects in the workspace, ask the user if they 369 // should be cleaned and built to run the new tooling 370 if (projects != null) { 371 if (MessageDialog.openQuestion(getShell(), PreferenceMessages.ApiUseScanPreferencePage_11, PreferenceMessages.ApiUseScanPreferencePage_12)) { 372 Util.getBuildJob(projects).schedule(); 373 } 374 } 375 } 376 if (hasLocationChanges && newCheckedLocations.length == 0) { 377 findAndDeleteAPIUseScanMarkers(); 378 } 379 380 setStoredValue(IApiCoreConstants.API_USE_SCAN_LOCATION, locations.toString()); 381 382 try { 383 fManager.applyChanges(); 384 } catch (BackingStoreException e) { 385 ApiUIPlugin.log(e); 386 } 387 } 388 findAndDeleteAPIUseScanMarkers()389 private void findAndDeleteAPIUseScanMarkers() { 390 IProject[] apiProjects = Util.getApiProjects(); 391 if (apiProjects == null) { 392 return; 393 } 394 for (IProject iProject : apiProjects) { 395 try { 396 iProject.deleteMarkers(IApiMarkerConstants.API_USESCAN_PROBLEM_MARKER, false, 397 IResource.DEPTH_INFINITE); 398 } catch (CoreException e) { 399 } 400 401 } 402 return; 403 } 404 405 /** 406 * Detects changes to the use scan locations 407 * 408 * @param newLocations 409 * @return if there have been changes to the use scan entries 410 */ hasLocationsChanges(String newLocations)411 private boolean hasLocationsChanges(String newLocations) { 412 String oldLocations = getStoredValue(IApiCoreConstants.API_USE_SCAN_LOCATION, null); 413 414 if (oldLocations != null && oldLocations.equalsIgnoreCase(newLocations)) { 415 return false; 416 } 417 418 ArrayList<String> oldCheckedElements = new ArrayList<>(); 419 if (oldLocations != null && oldLocations.length() > 0) { 420 String[] locations = oldLocations.split(UseScanManager.ESCAPE_REGEX + UseScanManager.LOCATION_DELIM); 421 for (String location : locations) { 422 String values[] = location.split(UseScanManager.ESCAPE_REGEX + UseScanManager.STATE_DELIM); 423 if (Boolean.parseBoolean(values[1])) { 424 oldCheckedElements.add(values[0]); 425 } 426 } 427 } 428 Object[] newCheckedLocations = fTableViewer.getCheckedElements(); 429 if (newCheckedLocations.length != oldCheckedElements.size()) { 430 return true; 431 } 432 for (int i = 0; i < newCheckedLocations.length; i++) { 433 if (!oldCheckedElements.contains(newCheckedLocations[i])) { 434 return true; 435 } 436 } 437 return false; 438 } 439 440 /** 441 * Sets the value to the given preference key 442 * 443 * @param key 444 * @param value 445 */ setStoredValue(String key, String value)446 public void setStoredValue(String key, String value) { 447 IEclipsePreferences node = getNode(); 448 if (value != null) { 449 node.put(key, value); 450 } else { 451 node.remove(key); 452 } 453 } 454 455 /** 456 * Retrieves the value for the given preference key or the returns the given 457 * default if it is not defined 458 * 459 * @param key 460 * @param defaultValue 461 * @return the stored value or the specified default 462 */ getStoredValue(String key, String defaultValue)463 public String getStoredValue(String key, String defaultValue) { 464 IEclipsePreferences node = getNode(); 465 if (node != null) { 466 return node.get(key, defaultValue); 467 } 468 return defaultValue; 469 } 470 471 /** 472 * @return the root preference node for the API tools core plug-in 473 */ getNode()474 private IEclipsePreferences getNode() { 475 IEclipsePreferences node = (InstanceScope.INSTANCE).getNode(ApiPlugin.PLUGIN_ID); 476 if (fManager != null) { 477 return fManager.getWorkingCopy(node); 478 } 479 return node; 480 } 481 } 482