1 /*******************************************************************************
2  * Copyright (c) 2000, 2016 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  *     Remy Chi Jian Suen <remy.suen@gmail.com> - Bug 175069 [Preferences] ResourceInfoPage is not setting dialog font on all widgets
14  *     Serge Beauchamp (Freescale Semiconductor) - [229633] Project Path Variable Support
15  *     Lars Vogel <Lars.Vogel@vogella.com> - Bug 474273
16  *     Simon Scholz <simon.scholz@vogella.com> - Bug 486777
17  *******************************************************************************/
18 package org.eclipse.ui.internal.ide.dialogs;
19 
20 import java.net.URI;
21 import java.net.URL;
22 import java.util.ArrayList;
23 import java.util.Collections;
24 import java.util.HashSet;
25 import java.util.Iterator;
26 import java.util.List;
27 import java.util.Set;
28 
29 import org.eclipse.core.commands.Command;
30 import org.eclipse.core.commands.ParameterizedCommand;
31 import org.eclipse.core.filesystem.EFS;
32 import org.eclipse.core.filesystem.IFileInfo;
33 import org.eclipse.core.filesystem.IFileStore;
34 import org.eclipse.core.filesystem.IFileSystem;
35 import org.eclipse.core.filesystem.URIUtil;
36 import org.eclipse.core.resources.IContainer;
37 import org.eclipse.core.resources.IFile;
38 import org.eclipse.core.resources.IFolder;
39 import org.eclipse.core.resources.IResource;
40 import org.eclipse.core.resources.ResourceAttributes;
41 import org.eclipse.core.runtime.Adapters;
42 import org.eclipse.core.runtime.CoreException;
43 import org.eclipse.core.runtime.FileLocator;
44 import org.eclipse.core.runtime.IPath;
45 import org.eclipse.core.runtime.NullProgressMonitor;
46 import org.eclipse.core.runtime.OperationCanceledException;
47 import org.eclipse.core.runtime.Path;
48 import org.eclipse.core.runtime.Status;
49 import org.eclipse.core.runtime.SubMonitor;
50 import org.eclipse.core.runtime.content.IContentDescription;
51 import org.eclipse.core.runtime.jobs.Job;
52 import org.eclipse.e4.core.commands.ECommandService;
53 import org.eclipse.e4.core.commands.EHandlerService;
54 import org.eclipse.jface.dialogs.Dialog;
55 import org.eclipse.jface.dialogs.ErrorDialog;
56 import org.eclipse.jface.dialogs.IDialogConstants;
57 import org.eclipse.jface.dialogs.MessageDialog;
58 import org.eclipse.jface.layout.GridDataFactory;
59 import org.eclipse.jface.preference.FieldEditor;
60 import org.eclipse.jface.resource.ImageDescriptor;
61 import org.eclipse.jface.resource.JFaceResources;
62 import org.eclipse.jface.resource.LocalResourceManager;
63 import org.eclipse.jface.resource.ResourceManager;
64 import org.eclipse.jface.window.Window;
65 import org.eclipse.osgi.util.NLS;
66 import org.eclipse.osgi.util.TextProcessor;
67 import org.eclipse.swt.SWT;
68 import org.eclipse.swt.custom.TableEditor;
69 import org.eclipse.swt.events.SelectionAdapter;
70 import org.eclipse.swt.events.SelectionEvent;
71 import org.eclipse.swt.events.SelectionListener;
72 import org.eclipse.swt.graphics.Font;
73 import org.eclipse.swt.layout.GridData;
74 import org.eclipse.swt.layout.GridLayout;
75 import org.eclipse.swt.widgets.Button;
76 import org.eclipse.swt.widgets.Composite;
77 import org.eclipse.swt.widgets.Control;
78 import org.eclipse.swt.widgets.Label;
79 import org.eclipse.swt.widgets.Table;
80 import org.eclipse.swt.widgets.TableColumn;
81 import org.eclipse.swt.widgets.TableItem;
82 import org.eclipse.swt.widgets.Text;
83 import org.eclipse.ui.PlatformUI;
84 import org.eclipse.ui.dialogs.PropertyPage;
85 import org.eclipse.ui.ide.dialogs.ResourceEncodingFieldEditor;
86 import org.eclipse.ui.internal.ide.IDEWorkbenchMessages;
87 import org.eclipse.ui.internal.ide.IDEWorkbenchPlugin;
88 import org.eclipse.ui.internal.ide.IIDEHelpContextIds;
89 import org.eclipse.ui.internal.ide.LineDelimiterEditor;
90 import org.eclipse.ui.internal.ide.handlers.ShowInSystemExplorerHandler;
91 import org.osgi.framework.Bundle;
92 import org.osgi.framework.FrameworkUtil;
93 
94 /**
95  * The ResourceInfoPage is the page that shows the basic info about the
96  * resource.
97  */
98 public class ResourceInfoPage extends PropertyPage {
99 	private interface IResourceChange {
getMessage()100 		public String getMessage();
101 
performChange(IResource resource)102 		public void performChange(IResource resource) throws CoreException;
103 	}
104 
105 	private Button editableBox;
106 
107 	private Button executableBox;
108 
109 	private Button archiveBox;
110 
111 	private Button derivedBox;
112 
113 	private Button immutableBox;
114 
115 	private Button permissionBoxes[];
116 
117 	private boolean previousReadOnlyValue;
118 
119 	private boolean previousExecutableValue;
120 
121 	private boolean previousArchiveValue;
122 
123 	private boolean previousDerivedValue;
124 
125 	private int previousPermissionsValue;
126 
127 	private IContentDescription cachedContentDescription;
128 
129 	private ResourceEncodingFieldEditor encodingEditor;
130 
131 	private LineDelimiterEditor lineDelimiterEditor;
132 
133 	private static String READ_ONLY = IDEWorkbenchMessages.ResourceInfo_readOnly;
134 
135 	private static String EXECUTABLE = IDEWorkbenchMessages.ResourceInfo_executable;
136 
137 	private static String LOCKED = IDEWorkbenchMessages.ResourceInfo_locked;
138 
139 	private static String ARCHIVE = IDEWorkbenchMessages.ResourceInfo_archive;
140 
141 	private static String DERIVED = IDEWorkbenchMessages.ResourceInfo_derived;
142 
143 	private static String DERIVED_HAS_DERIVED_ANCESTOR = IDEWorkbenchMessages.ResourceInfo_derivedHasDerivedAncestor;
144 
145 	private static String TYPE_TITLE = IDEWorkbenchMessages.ResourceInfo_type;
146 
147 	private static String LOCATION_TITLE = IDEWorkbenchMessages.ResourceInfo_location;
148 
149 	private static String LOCATION_BUTTON_TOOLTIP = IDEWorkbenchMessages.ResourceInfo_location_button_tooltip;
150 
151 	private static String RESOLVED_LOCATION_TITLE = IDEWorkbenchMessages.ResourceInfo_resolvedLocation;
152 
153 	private static String SIZE_TITLE = IDEWorkbenchMessages.ResourceInfo_size;
154 
155 	private static String PATH_TITLE = IDEWorkbenchMessages.ResourceInfo_path;
156 
157 	private static String TIMESTAMP_TITLE = IDEWorkbenchMessages.ResourceInfo_lastModified;
158 
159 	private static String FILE_ENCODING_TITLE = IDEWorkbenchMessages.WorkbenchPreference_encoding;
160 
161 	private static String CONTAINER_ENCODING_TITLE = IDEWorkbenchMessages.ResourceInfo_fileEncodingTitle;
162 
163 	private static String EDIT_TITLE = IDEWorkbenchMessages.ResourceInfo_edit;
164 
165 	private Text resolvedLocationValue = null;
166 	private Text locationValue = null;
167 	private Text sizeValue = null;
168 	private IPath newResourceLocation = null;
169 
170 	// Max value width in characters before wrapping
171 	private static final int MAX_VALUE_WIDTH = 80;
172 
173 	/**
174 	 * Create the group that shows the name, location, size and type.
175 	 *
176 	 * @param parent
177 	 *            the composite the group will be created in
178 	 * @param resource
179 	 *            the resource the information is being taken from.
180 	 * @return the composite for the group
181 	 */
createBasicInfoGroup(Composite parent, IResource resource)182 	private Composite createBasicInfoGroup(Composite parent, IResource resource) {
183 		initializeDialogUnits(parent);
184 
185 		Composite basicInfoComposite = new Composite(parent, SWT.NULL);
186 		GridLayout layout = new GridLayout();
187 		layout.numColumns = 3;
188 		layout.marginWidth = 0;
189 		layout.marginHeight = 0;
190 		basicInfoComposite.setLayout(layout);
191 		GridData data = new GridData();
192 		data.verticalAlignment = GridData.FILL;
193 		data.horizontalAlignment = GridData.FILL;
194 		basicInfoComposite.setLayoutData(data);
195 
196 		// The group for path
197 		Label pathLabel = new Label(basicInfoComposite, SWT.NONE);
198 		pathLabel.setText(PATH_TITLE);
199 		GridData gd = new GridData();
200 		gd.verticalAlignment = SWT.TOP;
201 		pathLabel.setLayoutData(gd);
202 
203 		// path value label
204 		Text pathValueText = new Text(basicInfoComposite, SWT.WRAP
205 				| SWT.READ_ONLY);
206 		String pathString = TextProcessor.process(resource.getFullPath()
207 				.toString());
208 		pathValueText.setText(pathString);
209 		gd = new GridData();
210 		gd.widthHint = convertWidthInCharsToPixels(MAX_VALUE_WIDTH);
211 		gd.grabExcessHorizontalSpace = true;
212 		gd.horizontalAlignment = GridData.FILL;
213 		gd.horizontalSpan = 2;
214 		pathValueText.setLayoutData(gd);
215 		pathValueText.setBackground(pathValueText.getDisplay().getSystemColor(
216 				SWT.COLOR_WIDGET_BACKGROUND));
217 
218 		// The group for types
219 		Label typeTitle = new Label(basicInfoComposite, SWT.LEFT);
220 		typeTitle.setText(TYPE_TITLE);
221 
222 		Text typeValue = new Text(basicInfoComposite, SWT.LEFT | SWT.READ_ONLY);
223 		GridDataFactory.swtDefaults().span(2, SWT.DEFAULT).applyTo(typeValue);
224 		typeValue.setText(IDEResourceInfoUtils.getTypeString(resource,
225 				getContentDescription(resource)));
226 		typeValue.setBackground(typeValue.getDisplay().getSystemColor(
227 				SWT.COLOR_WIDGET_BACKGROUND));
228 
229 		if (resource.isLinked() && !resource.isVirtual()) {
230 			// The group for location
231 			Label locationTitle = new Label(basicInfoComposite, SWT.LEFT);
232 			locationTitle.setText(LOCATION_TITLE);
233 			gd = new GridData();
234 			gd.verticalAlignment = SWT.TOP;
235 			locationTitle.setLayoutData(gd);
236 
237 			Composite locationComposite = new Composite(basicInfoComposite,
238 					SWT.NULL);
239 			layout = new GridLayout();
240 			layout.numColumns = 2;
241 			layout.marginWidth = 0;
242 			layout.marginHeight = 0;
243 			locationComposite.setLayout(layout);
244 			gd = new GridData();
245 			gd.widthHint = convertWidthInCharsToPixels(MAX_VALUE_WIDTH);
246 			gd.grabExcessHorizontalSpace = true;
247 			gd.verticalAlignment = SWT.TOP;
248 			gd.horizontalAlignment = GridData.FILL;
249 			gd.horizontalSpan = 2;
250 			locationComposite.setLayoutData(gd);
251 
252 			locationValue = new Text(locationComposite, SWT.WRAP
253 					| SWT.READ_ONLY);
254 			String locationStr = TextProcessor.process(IDEResourceInfoUtils
255 					.getLocationText(resource));
256 			locationValue.setText(locationStr);
257 			gd = new GridData();
258 			gd.widthHint = convertWidthInCharsToPixels(MAX_VALUE_WIDTH);
259 			gd.grabExcessHorizontalSpace = true;
260 			gd.verticalAlignment = SWT.TOP;
261 			gd.horizontalAlignment = GridData.FILL;
262 			locationValue.setLayoutData(gd);
263 			locationValue.setBackground(locationValue.getDisplay().getSystemColor(
264 					SWT.COLOR_WIDGET_BACKGROUND));
265 
266 			Button editButton = new Button(locationComposite, SWT.PUSH);
267 			editButton.setText(EDIT_TITLE);
268 			setButtonLayoutData(editButton);
269 			((GridData) editButton.getLayoutData()).verticalAlignment = SWT.TOP;
270 			int locationValueHeight = locationValue.computeSize(SWT.DEFAULT, SWT.DEFAULT, true).y;
271 			int editButtonHeight = editButton.computeSize(SWT.DEFAULT, SWT.DEFAULT, true).y;
272 			int verticalIndent = (editButtonHeight - locationValueHeight) / 2 ;
273 			((GridData) locationTitle.getLayoutData()).verticalIndent = verticalIndent;
274 			((GridData) locationValue.getLayoutData()).verticalIndent = verticalIndent;
275 			editButton.addSelectionListener(new SelectionListener() {
276 				@Override
277 				public void widgetDefaultSelected(SelectionEvent e) {
278 					editLinkLocation();
279 				}
280 
281 				@Override
282 				public void widgetSelected(SelectionEvent e) {
283 					editLinkLocation();
284 				}
285 			});
286 
287 			// displayed in all cases since the link can be changed to a path variable any time by the user in this dialog
288 			Label resolvedLocationTitle = new Label(basicInfoComposite,
289 					SWT.LEFT);
290 			resolvedLocationTitle.setText(RESOLVED_LOCATION_TITLE);
291 			gd = new GridData();
292 			gd.verticalAlignment = SWT.TOP;
293 			resolvedLocationTitle.setLayoutData(gd);
294 
295 			resolvedLocationValue = new Text(basicInfoComposite, SWT.WRAP
296 					| SWT.READ_ONLY);
297 			resolvedLocationValue.setText(IDEResourceInfoUtils
298 					.getResolvedLocationText(resource));
299 			gd = new GridData();
300 			gd.widthHint = convertWidthInCharsToPixels(MAX_VALUE_WIDTH);
301 			gd.grabExcessHorizontalSpace = true;
302 			gd.horizontalAlignment = GridData.FILL;
303 			gd.horizontalSpan = 2;
304 			resolvedLocationValue.setLayoutData(gd);
305 			resolvedLocationValue.setBackground(resolvedLocationValue
306 					.getDisplay().getSystemColor(SWT.COLOR_WIDGET_BACKGROUND));
307 		} else {
308 			if (!resource.isVirtual()) {
309 				// The group for location
310 				Label locationTitle = new Label(basicInfoComposite, SWT.LEFT);
311 				locationTitle.setText(LOCATION_TITLE);
312 				gd = new GridData();
313 				locationTitle.setLayoutData(gd);
314 
315 				Text locationValue = new Text(basicInfoComposite, SWT.WRAP
316 						| SWT.READ_ONLY);
317 				final String locationStr = TextProcessor.process(IDEResourceInfoUtils
318 						.getLocationText(resource));
319 				locationValue.setText(locationStr);
320 				gd = new GridData();
321 				gd.horizontalAlignment = GridData.FILL;
322 				locationValue.setLayoutData(gd);
323 				locationValue.setBackground(locationValue.getDisplay()
324 						.getSystemColor(SWT.COLOR_WIDGET_BACKGROUND));
325 
326 				Button goToLocationButton = new Button(basicInfoComposite, SWT.PUSH);
327 				gd = new GridData();
328 				gd.verticalAlignment = SWT.TOP;
329 				goToLocationButton.setLayoutData(gd);
330 				ResourceManager resourceManager = new LocalResourceManager(JFaceResources.getResources(),
331 						goToLocationButton);
332 				Bundle bundle = FrameworkUtil.getBundle(getClass());
333 				URL goToFolderUrl = FileLocator.find(bundle, new Path("icons/full/obj16/goto_input.png"), //$NON-NLS-1$
334 						null);
335 				goToLocationButton.setImage(resourceManager.createImage(ImageDescriptor.createFromURL(goToFolderUrl)));
336 				goToLocationButton.setToolTipText(LOCATION_BUTTON_TOOLTIP);
337 				goToLocationButton.addSelectionListener(new SelectionAdapter() {
338 
339 					@SuppressWarnings("restriction")
340 					@Override
341 					public void widgetSelected(SelectionEvent e) {
342 						ECommandService commandService = PlatformUI.getWorkbench().getService(ECommandService.class);
343 						EHandlerService handlerService = PlatformUI.getWorkbench().getService(EHandlerService.class);
344 
345 						Command command = commandService.getCommand(ShowInSystemExplorerHandler.ID);
346 						if (command.isDefined()) {
347 							ParameterizedCommand parameterizedCommand = commandService
348 									.createCommand(ShowInSystemExplorerHandler.ID, Collections.singletonMap(
349 											ShowInSystemExplorerHandler.RESOURCE_PATH_PARAMETER, locationStr));
350 							if (handlerService.canExecute(parameterizedCommand)) {
351 								handlerService.executeHandler(parameterizedCommand);
352 							}
353 						}
354 					}
355 				});
356 			}
357 		}
358 		if (resource.getType() == IResource.FILE) {
359 			// The group for size
360 			Label sizeTitle = new Label(basicInfoComposite, SWT.LEFT);
361 			sizeTitle.setText(SIZE_TITLE);
362 
363 			Text sizeValue = new Text(basicInfoComposite, SWT.LEFT
364 					| SWT.READ_ONLY);
365 			sizeValue.setText(IDEResourceInfoUtils.getSizeString(resource));
366 			gd = new GridData();
367 			gd.widthHint = convertWidthInCharsToPixels(MAX_VALUE_WIDTH);
368 			gd.grabExcessHorizontalSpace = true;
369 			gd.horizontalAlignment = GridData.FILL;
370 			gd.horizontalSpan = 2;
371 			sizeValue.setLayoutData(gd);
372 			sizeValue.setBackground(sizeValue.getDisplay().getSystemColor(
373 					SWT.COLOR_WIDGET_BACKGROUND));
374 		}
375 
376 		Label timeStampLabel = new Label(basicInfoComposite, SWT.NONE);
377 		timeStampLabel.setText(TIMESTAMP_TITLE);
378 
379 		// timeStamp value label
380 		Text timeStampValue = new Text(basicInfoComposite, SWT.READ_ONLY);
381 		timeStampValue.setText(IDEResourceInfoUtils
382 				.getDateStringValue(resource));
383 		timeStampValue.setBackground(timeStampValue.getDisplay()
384 				.getSystemColor(SWT.COLOR_WIDGET_BACKGROUND));
385 		GridData gridData = new GridData(GridData.FILL_HORIZONTAL | GridData.GRAB_HORIZONTAL);
386 		gridData.horizontalSpan = 2;
387 		timeStampValue.setLayoutData(gridData);
388 
389 		return basicInfoComposite;
390 	}
391 
editLinkLocation()392 	protected void editLinkLocation() {
393 		IResource resource = Adapters.adapt(getElement(), IResource.class);
394 		String locationFormat = resource.getPathVariableManager().convertFromUserEditableFormat(locationValue.getText(), true);
395 		IPath location = Path.fromOSString(locationFormat);
396 
397 		PathVariableDialog dialog = new PathVariableDialog(getShell(),
398 				PathVariableDialog.EDIT_LINK_LOCATION, resource.getType(),
399 				resource.getPathVariableManager(), null);
400 		dialog.setLinkLocation(location);
401 		dialog.setResource(resource);
402 		// opens the dialog - just returns if the user cancels it
403 		if (dialog.open() == Window.CANCEL) {
404 			return;
405 		}
406 		location = Path.fromOSString(dialog.getVariableValue());
407 		newResourceLocation = location;
408 		refreshLinkLocation();
409 	}
410 
refreshLinkLocation()411 	private void refreshLinkLocation() {
412 		IResource resource = Adapters.adapt(getElement(), IResource.class);
413 
414 		String userEditableFormat = resource.getPathVariableManager().convertToUserEditableFormat(newResourceLocation.toOSString(), true);
415 		locationValue.setText(userEditableFormat);
416 
417 		URI resolvedURI = resource.getPathVariableManager()
418 				.resolveURI(URIUtil.toURI(newResourceLocation));
419 		IPath resolved = URIUtil.toPath(resolvedURI);
420 		if (!IDEResourceInfoUtils.exists(resolved.toOSString())) {
421 			resolvedLocationValue
422 					.setText(IDEWorkbenchMessages.ResourceInfo_undefinedPathVariable);
423 			if (sizeValue != null)
424 				sizeValue.setText(IDEWorkbenchMessages.ResourceInfo_notExist);
425 		} else {
426 			resolvedLocationValue.setText(resolved.toPortableString());
427 			if (sizeValue != null) {
428 				IFileInfo info = IDEResourceInfoUtils.getFileInfo(resolved
429 						.toPortableString());
430 				if (info != null)
431 					sizeValue.setText(NLS.bind(
432 							IDEWorkbenchMessages.ResourceInfo_bytes, Long
433 									.toString(info.getLength())));
434 				else
435 					sizeValue
436 							.setText(IDEWorkbenchMessages.ResourceInfo_unknown);
437 			}
438 		}
439 	}
440 
441 	@Override
createContents(Composite parent)442 	protected Control createContents(Composite parent) {
443 
444 		PlatformUI.getWorkbench().getHelpSystem().setHelp(getControl(),
445 				IIDEHelpContextIds.RESOURCE_INFO_PROPERTY_PAGE);
446 
447 		// layout the page
448 		IResource resource = Adapters.adapt(getElement(), IResource.class);
449 
450 		if (resource == null) {
451 			Label label = new Label(parent, SWT.NONE);
452 			label.setText(IDEWorkbenchMessages.ResourceInfoPage_noResource);
453 			return label;
454 		}
455 
456 		if (resource.getType() != IResource.PROJECT) {
457 			ResourceAttributes attrs = resource.getResourceAttributes();
458 			if (attrs != null) {
459 				previousReadOnlyValue = attrs.isReadOnly();
460 				previousExecutableValue = attrs.isExecutable();
461 				previousArchiveValue = attrs.isArchive();
462 			} else {
463 				previousReadOnlyValue = previousExecutableValue = previousArchiveValue = false;
464 			}
465 			previousDerivedValue = resource.isDerived();
466 		}
467 
468 		// top level group
469 		Composite composite = new Composite(parent, SWT.NONE);
470 		GridLayout layout = new GridLayout();
471 		layout.marginWidth = 0;
472 		layout.marginHeight = 0;
473 		composite.setLayout(layout);
474 		GridData data = new GridData(GridData.FILL);
475 		data.grabExcessHorizontalSpace = true;
476 		composite.setLayoutData(data);
477 
478 		createBasicInfoGroup(composite, resource);
479 		// Attributes are not relevant to projects
480 		if (resource.getType() != IResource.PROJECT) {
481 			createSeparator(composite);
482 			int fsAttributes = getFileSystemAttributes(resource);
483 			if (isPermissionsSupport(fsAttributes))
484 				previousPermissionsValue = fetchPermissions(resource);
485 			createStateGroup(composite, resource, fsAttributes);
486 			if (isPermissionsSupport(fsAttributes)) {
487 				createSeparator(composite);
488 				createPermissionsGroup(composite);
489 				setPermissionsSelection(previousPermissionsValue);
490 			}
491 		}
492 		//We can't save and load the preferences for closed project
493 		if (resource.getProject().isOpen()) {
494 			encodingEditor = new ResourceEncodingFieldEditor(
495 					getFieldEditorLabel(resource), composite, resource);
496 			encodingEditor.setPage(this);
497 			encodingEditor.load();
498 
499 			encodingEditor.setPropertyChangeListener(event -> {
500 				if (event.getProperty().equals(FieldEditor.IS_VALID)) {
501 					setValid(encodingEditor.isValid());
502 				}
503 			});
504 
505 			if (resource.getType() == IResource.PROJECT) {
506 				lineDelimiterEditor = new LineDelimiterEditor(composite, resource
507 						.getProject());
508 				lineDelimiterEditor.doLoad();
509 			}
510 		}
511 
512 		Dialog.applyDialogFont(composite);
513 
514 		return composite;
515 	}
516 
fetchPermissions(IResource resource)517 	private int fetchPermissions(IResource resource) {
518 		IFileStore store = null;
519 		try {
520 			store = EFS.getStore(resource.getLocationURI());
521 		} catch (CoreException e) {
522 			return 0;
523 		}
524 		IFileInfo info = store.fetchInfo();
525 		int permissions = 0;
526 		if (info.exists()) {
527 			permissions |= info.getAttribute(EFS.ATTRIBUTE_OWNER_READ) ? EFS.ATTRIBUTE_OWNER_READ
528 					: 0;
529 			permissions |= info.getAttribute(EFS.ATTRIBUTE_OWNER_WRITE) ? EFS.ATTRIBUTE_OWNER_WRITE
530 					: 0;
531 			permissions |= info.getAttribute(EFS.ATTRIBUTE_OWNER_EXECUTE) ? EFS.ATTRIBUTE_OWNER_EXECUTE
532 					: 0;
533 			permissions |= info.getAttribute(EFS.ATTRIBUTE_GROUP_READ) ? EFS.ATTRIBUTE_GROUP_READ
534 					: 0;
535 			permissions |= info.getAttribute(EFS.ATTRIBUTE_GROUP_WRITE) ? EFS.ATTRIBUTE_GROUP_WRITE
536 					: 0;
537 			permissions |= info.getAttribute(EFS.ATTRIBUTE_GROUP_EXECUTE) ? EFS.ATTRIBUTE_GROUP_EXECUTE
538 					: 0;
539 			permissions |= info.getAttribute(EFS.ATTRIBUTE_OTHER_READ) ? EFS.ATTRIBUTE_OTHER_READ
540 					: 0;
541 			permissions |= info.getAttribute(EFS.ATTRIBUTE_OTHER_WRITE) ? EFS.ATTRIBUTE_OTHER_WRITE
542 					: 0;
543 			permissions |= info.getAttribute(EFS.ATTRIBUTE_OTHER_EXECUTE) ? EFS.ATTRIBUTE_OTHER_EXECUTE
544 					: 0;
545 			permissions |= info.getAttribute(EFS.ATTRIBUTE_IMMUTABLE) ? EFS.ATTRIBUTE_IMMUTABLE
546 					: 0;
547 		}
548 		return permissions;
549 	}
550 
getDefaulPermissions(boolean folder)551 	private int getDefaulPermissions(boolean folder) {
552 		int permissions = EFS.ATTRIBUTE_OWNER_READ | EFS.ATTRIBUTE_OWNER_WRITE
553 				| EFS.ATTRIBUTE_GROUP_READ | EFS.ATTRIBUTE_GROUP_WRITE
554 				| EFS.ATTRIBUTE_OTHER_READ;
555 		if (folder)
556 			permissions |= EFS.ATTRIBUTE_OWNER_EXECUTE
557 					| EFS.ATTRIBUTE_GROUP_EXECUTE | EFS.ATTRIBUTE_OTHER_EXECUTE;
558 		return permissions;
559 	}
560 
setPermissionsSelection(int permissions)561 	private void setPermissionsSelection(int permissions) {
562 		permissionBoxes[0].setSelection((permissions & EFS.ATTRIBUTE_OWNER_READ) != 0);
563 		permissionBoxes[1].setSelection((permissions & EFS.ATTRIBUTE_OWNER_WRITE) != 0);
564 		permissionBoxes[2].setSelection((permissions & EFS.ATTRIBUTE_OWNER_EXECUTE) != 0);
565 		permissionBoxes[3].setSelection((permissions & EFS.ATTRIBUTE_GROUP_READ) != 0);
566 		permissionBoxes[4].setSelection((permissions & EFS.ATTRIBUTE_GROUP_WRITE) != 0);
567 		permissionBoxes[5].setSelection((permissions & EFS.ATTRIBUTE_GROUP_EXECUTE) != 0);
568 		permissionBoxes[6].setSelection((permissions & EFS.ATTRIBUTE_OTHER_READ) != 0);
569 		permissionBoxes[7].setSelection((permissions & EFS.ATTRIBUTE_OTHER_WRITE) != 0);
570 		permissionBoxes[8].setSelection((permissions & EFS.ATTRIBUTE_OTHER_EXECUTE) != 0);
571 		if (immutableBox != null)
572 			immutableBox.setSelection((permissions & EFS.ATTRIBUTE_IMMUTABLE) != 0);
573 	}
574 
getPermissionsSelection()575 	private int getPermissionsSelection() {
576 		int permissions = 0;
577 		permissions |= permissionBoxes[0].getSelection() ? EFS.ATTRIBUTE_OWNER_READ : 0;
578 		permissions |= permissionBoxes[1].getSelection() ? EFS.ATTRIBUTE_OWNER_WRITE : 0;
579 		permissions |= permissionBoxes[2].getSelection() ? EFS.ATTRIBUTE_OWNER_EXECUTE : 0;
580 		permissions |= permissionBoxes[3].getSelection() ? EFS.ATTRIBUTE_GROUP_READ : 0;
581 		permissions |= permissionBoxes[4].getSelection() ? EFS.ATTRIBUTE_GROUP_WRITE : 0;
582 		permissions |= permissionBoxes[5].getSelection() ? EFS.ATTRIBUTE_GROUP_EXECUTE : 0;
583 		permissions |= permissionBoxes[6].getSelection() ? EFS.ATTRIBUTE_OTHER_READ : 0;
584 		permissions |= permissionBoxes[7].getSelection() ? EFS.ATTRIBUTE_OTHER_WRITE : 0;
585 		permissions |= permissionBoxes[8].getSelection() ? EFS.ATTRIBUTE_OTHER_EXECUTE : 0;
586 		if (immutableBox != null)
587 			permissions |= immutableBox.getSelection() ? EFS.ATTRIBUTE_IMMUTABLE : 0;
588 		return permissions;
589 	}
590 
putPermissions(IResource resource, int permissions)591 	private boolean putPermissions(IResource resource, int permissions) {
592 		IFileStore store = null;
593 		try {
594 			store = EFS.getStore(resource.getLocationURI());
595 		} catch (CoreException e) {
596 			return false;
597 		}
598 		IFileInfo fileInfo = store.fetchInfo();
599 		if (!fileInfo.exists())
600 			return false;
601 		fileInfo.setAttribute(EFS.ATTRIBUTE_OWNER_READ, (permissions & EFS.ATTRIBUTE_OWNER_READ) != 0);
602 		fileInfo.setAttribute(EFS.ATTRIBUTE_OWNER_WRITE, (permissions & EFS.ATTRIBUTE_OWNER_WRITE) != 0);
603 		fileInfo.setAttribute(EFS.ATTRIBUTE_OWNER_EXECUTE, (permissions & EFS.ATTRIBUTE_OWNER_EXECUTE) != 0);
604 		fileInfo.setAttribute(EFS.ATTRIBUTE_GROUP_READ, (permissions & EFS.ATTRIBUTE_GROUP_READ) != 0);
605 		fileInfo.setAttribute(EFS.ATTRIBUTE_GROUP_WRITE, (permissions & EFS.ATTRIBUTE_GROUP_WRITE) != 0);
606 		fileInfo.setAttribute(EFS.ATTRIBUTE_GROUP_EXECUTE, (permissions & EFS.ATTRIBUTE_GROUP_EXECUTE) != 0);
607 		fileInfo.setAttribute(EFS.ATTRIBUTE_OTHER_READ, (permissions & EFS.ATTRIBUTE_OTHER_READ) != 0);
608 		fileInfo.setAttribute(EFS.ATTRIBUTE_OTHER_WRITE, (permissions & EFS.ATTRIBUTE_OTHER_WRITE) != 0);
609 		fileInfo.setAttribute(EFS.ATTRIBUTE_OTHER_EXECUTE, (permissions & EFS.ATTRIBUTE_OTHER_EXECUTE) != 0);
610 		fileInfo.setAttribute(EFS.ATTRIBUTE_IMMUTABLE, (permissions & EFS.ATTRIBUTE_IMMUTABLE) != 0);
611 		try {
612 			store.putInfo(fileInfo, EFS.SET_ATTRIBUTES, null);
613 		} catch (CoreException e) {
614 			return false;
615 		}
616 		return true;
617 	}
618 
619 	/**
620 	 * Return the label for the encoding field editor for the resource.
621 	 *
622 	 * @param resource -
623 	 *            the resource to edit.
624 	 * @return String
625 	 */
getFieldEditorLabel(IResource resource)626 	private String getFieldEditorLabel(IResource resource) {
627 		if (resource instanceof IContainer) {
628 			return CONTAINER_ENCODING_TITLE;
629 		}
630 		return FILE_ENCODING_TITLE;
631 	}
632 
633 	/**
634 	 * Create the isEditable button and it's associated label as a child of
635 	 * parent using the editableValue of the receiver. The Composite will be the
636 	 * parent of the button.
637 	 *
638 	 * @param composite
639 	 *            the parent of the button
640 	 */
createEditableButton(Composite composite)641 	private void createEditableButton(Composite composite) {
642 
643 		this.editableBox = new Button(composite, SWT.CHECK | SWT.RIGHT);
644 		this.editableBox.setAlignment(SWT.LEFT);
645 		this.editableBox.setText(READ_ONLY);
646 		this.editableBox.setSelection(this.previousReadOnlyValue);
647 	}
648 
649 	/**
650 	 * Create the isExecutable button and it's associated label as a child of
651 	 * parent using the editableValue of the receiver. The Composite will be the
652 	 * parent of the button.
653 	 *
654 	 * @param composite
655 	 *            the parent of the button
656 	 */
createExecutableButton(Composite composite)657 	private void createExecutableButton(Composite composite) {
658 
659 		this.executableBox = new Button(composite, SWT.CHECK | SWT.RIGHT);
660 		this.executableBox.setAlignment(SWT.LEFT);
661 		this.executableBox.setText(EXECUTABLE);
662 		this.executableBox.setSelection(this.previousExecutableValue);
663 	}
664 
665 	/**
666 	 * Create the isLocked button and it's associated label as a child of
667 	 * parent using the editableValue of the receiver. The Composite will be the
668 	 * parent of the button.
669 	 *
670 	 * @param composite
671 	 *            the parent of the button
672 	 */
createImmutableButton(Composite composite)673 	private void createImmutableButton(Composite composite) {
674 		this.immutableBox = new Button(composite, SWT.CHECK | SWT.RIGHT);
675 		this.immutableBox.setAlignment(SWT.LEFT);
676 		this.immutableBox.setText(LOCKED);
677 		this.immutableBox.setSelection((this.previousPermissionsValue & EFS.ATTRIBUTE_IMMUTABLE) != 0);
678 	}
679 
680 	/**
681 	 * Create the isArchive button and it's associated label as a child of
682 	 * parent using the editableValue of the receiver. The Composite will be the
683 	 * parent of the button.
684 	 *
685 	 * @param composite
686 	 *            the parent of the button
687 	 */
createArchiveButton(Composite composite)688 	private void createArchiveButton(Composite composite) {
689 
690 		this.archiveBox = new Button(composite, SWT.CHECK | SWT.RIGHT);
691 		this.archiveBox.setAlignment(SWT.LEFT);
692 		this.archiveBox.setText(ARCHIVE);
693 		this.archiveBox.setSelection(this.previousArchiveValue);
694 	}
695 
696 	/**
697 	 * Create the derived button and it's associated label as a child of parent
698 	 * using the derived of the receiver. The Composite will be the parent of
699 	 * the button.
700 	 *
701 	 * @param composite
702 	 *            the parent of the button
703 	 * @param resource
704 	 *            the resource the information is being taken from
705 	 */
createDerivedButton(Composite composite, IResource resource)706 	private void createDerivedButton(Composite composite, IResource resource) {
707 
708 		this.derivedBox = new Button(composite, SWT.CHECK | SWT.RIGHT);
709 		this.derivedBox.setAlignment(SWT.LEFT);
710 		if (resource.getParent().isDerived(IResource.CHECK_ANCESTORS))
711 			this.derivedBox.setText(DERIVED_HAS_DERIVED_ANCESTOR);
712 		else
713 			this.derivedBox.setText(DERIVED);
714 		this.derivedBox.setSelection(this.previousDerivedValue);
715 	}
716 
717 	/**
718 	 * Create a separator that goes across the entire page
719 	 *
720 	 * @param composite
721 	 *            The parent of the seperator
722 	 */
createSeparator(Composite composite)723 	private void createSeparator(Composite composite) {
724 
725 		Label separator = new Label(composite, SWT.SEPARATOR | SWT.HORIZONTAL);
726 		GridData gridData = new GridData();
727 		gridData.horizontalAlignment = GridData.FILL;
728 		gridData.grabExcessHorizontalSpace = true;
729 		separator.setLayoutData(gridData);
730 	}
731 
732 	/**
733 	 * Create the group that shows the read only state and the timestamp.
734 	 *
735 	 * @param parent
736 	 *            the composite the group will be created in
737 	 * @param resource
738 	 *            the resource the information is being taken from.
739 	 */
createStateGroup(Composite parent, IResource resource, int fsAttributes)740 	private void createStateGroup(Composite parent, IResource resource, int fsAttributes) {
741 		Font font = parent.getFont();
742 
743 		Composite composite = new Composite(parent, SWT.NULL);
744 		GridLayout layout = new GridLayout();
745 		layout.numColumns = 1;
746 		layout.marginWidth = 0;
747 		layout.marginHeight = 0;
748 		composite.setLayout(layout);
749 		GridData data = new GridData();
750 		data.horizontalAlignment = GridData.FILL;
751 		composite.setLayoutData(data);
752 
753 		Label attributesLabel = new Label(composite, SWT.LEFT);
754 		attributesLabel.setText(IDEWorkbenchMessages.ResourceInfo_attributes);
755 
756 		if (!resource.isVirtual()) {
757 			if ((fsAttributes & EFS.ATTRIBUTE_READ_ONLY) != 0
758 					&& !isPermissionsSupport(fsAttributes))
759 				createEditableButton(composite);
760 			if ((fsAttributes & EFS.ATTRIBUTE_EXECUTABLE) != 0
761 					&& !isPermissionsSupport(fsAttributes))
762 				createExecutableButton(composite);
763 			if ((fsAttributes & EFS.ATTRIBUTE_ARCHIVE) != 0)
764 				createArchiveButton(composite);
765 			if ((fsAttributes & EFS.ATTRIBUTE_IMMUTABLE) != 0)
766 				createImmutableButton(composite);
767 		}
768 		createDerivedButton(composite, resource);
769 		// create warning for executable flag
770 		if (executableBox != null && resource.getType() == IResource.FOLDER)
771 			createExecutableWarning(composite, font);
772 	}
773 
createPermissionsGroup(Composite parent)774 	private void createPermissionsGroup(Composite parent) {
775 		Font font = parent.getFont();
776 
777 		permissionBoxes = new Button[9];
778 		Composite composite = new Composite(parent, SWT.NULL);
779 		GridLayout layout = new GridLayout();
780 		layout.numColumns = 1;
781 		layout.marginWidth = 0;
782 		layout.marginHeight = 0;
783 		composite.setLayout(layout);
784 		GridData data = new GridData();
785 		data.horizontalAlignment = GridData.FILL;
786 		data.grabExcessHorizontalSpace = true;
787 		composite.setLayoutData(data);
788 
789 		Label permissionsLabel = new Label(composite, SWT.NONE);
790 		permissionsLabel.setText(IDEWorkbenchMessages.ResourceInfo_permissions);
791 
792 		Table table = new Table(composite, SWT.BORDER);
793 		table.setHeaderVisible(true);
794 		table.setLinesVisible(true);
795 		for (int i = 0; i < 4; i++) {
796 			new TableColumn(table, SWT.NONE).setResizable(false);
797 		}
798 		table.getColumn(1).setText(IDEWorkbenchMessages.ResourceInfo_read);
799 		table.getColumn(2).setText(IDEWorkbenchMessages.ResourceInfo_write);
800 		table.getColumn(3).setText(IDEWorkbenchMessages.ResourceInfo_execute);
801 
802 		table.getColumn(3).pack();
803 		int columnWidth = table.getColumn(3).getWidth();
804 		table.getColumn(0).setWidth(columnWidth);
805 		table.getColumn(1).setWidth(columnWidth);
806 		table.getColumn(2).setWidth(columnWidth);
807 
808 		TableItem ownerItem = new TableItem(table, SWT.NONE);
809 		ownerItem.setText(IDEWorkbenchMessages.ResourceInfo_owner);
810 		permissionBoxes[0] = createPermissionEditor(table, ownerItem, 1);
811 		permissionBoxes[1] = createPermissionEditor(table, ownerItem, 2);
812 		permissionBoxes[2] = createPermissionEditor(table, ownerItem, 3);
813 
814 		TableItem groupItem = new TableItem(table, SWT.NONE);
815 		groupItem.setText(IDEWorkbenchMessages.ResourceInfo_group);
816 		permissionBoxes[3] = createPermissionEditor(table, groupItem, 1);
817 		permissionBoxes[4] = createPermissionEditor(table, groupItem, 2);
818 		permissionBoxes[5] = createPermissionEditor(table, groupItem, 3);
819 
820 		TableItem otherItem = new TableItem(table, SWT.NONE);
821 		otherItem.setText(IDEWorkbenchMessages.ResourceInfo_other);
822 		permissionBoxes[6] = createPermissionEditor(table, otherItem, 1);
823 		permissionBoxes[7] = createPermissionEditor(table, otherItem, 2);
824 		permissionBoxes[8] = createPermissionEditor(table, otherItem, 3);
825 
826 		GridData tableData = new GridData();
827 		tableData.heightHint = table.getHeaderHeight() + 3 * table.getItemHeight();
828 		table.setLayoutData(tableData);
829 		createExecutableWarning(composite, font);
830 	}
831 
createPermissionEditor(Table table, TableItem item, int index)832 	private Button createPermissionEditor(Table table, TableItem item, int index) {
833 		Button button = new Button(table, SWT.CHECK);
834 		button.pack();
835 		TableEditor editor = new TableEditor(table);
836 		editor.grabVertical = true;
837 		editor.verticalAlignment = SWT.CENTER;
838 		editor.minimumWidth = button.getSize().x;
839 		editor.setEditor(button, item, index);
840 		editor.getEditor();
841 		return button;
842 	}
843 
createExecutableWarning(Composite composite, Font font)844 	private Composite createExecutableWarning(Composite composite, Font font) {
845 		Composite noteComposite = createNoteComposite(font, composite,
846 				IDEWorkbenchMessages.Preference_note,
847 				IDEWorkbenchMessages.ResourceInfo_exWarning);
848 		GridData data = new GridData();
849 		data.widthHint = convertWidthInCharsToPixels(IDEWorkbenchMessages.ResourceInfo_exWarning.length());
850 		data.grabExcessHorizontalSpace = true;
851 		data.horizontalAlignment = GridData.FILL;
852 		noteComposite.setLayoutData(data);
853 		return noteComposite;
854 	}
855 
getFileSystemAttributes(IResource resource)856 	private int getFileSystemAttributes(IResource resource) {
857 		URI location = resource.getLocationURI();
858 		if (location == null || location.getScheme() == null)
859 			return 0;
860 		IFileSystem fs;
861 		try {
862 			fs = EFS.getFileSystem(location.getScheme());
863 		} catch (CoreException e) {
864 			return 0;
865 		}
866 		return fs.attributes();
867 	}
868 
isPermissionsSupport(int fsAttributes)869 	private boolean isPermissionsSupport(int fsAttributes) {
870 		int unixPermissions = EFS.ATTRIBUTE_OWNER_READ
871 				| EFS.ATTRIBUTE_OWNER_WRITE | EFS.ATTRIBUTE_OWNER_EXECUTE
872 				| EFS.ATTRIBUTE_GROUP_READ | EFS.ATTRIBUTE_GROUP_WRITE
873 				| EFS.ATTRIBUTE_GROUP_EXECUTE | EFS.ATTRIBUTE_OTHER_READ
874 				| EFS.ATTRIBUTE_OTHER_WRITE | EFS.ATTRIBUTE_OTHER_EXECUTE;
875 		if ((fsAttributes & unixPermissions) == unixPermissions)
876 			return true;
877 		return false;
878 	}
879 
getContentDescription(IResource resource)880 	private IContentDescription getContentDescription(IResource resource) {
881 		if (resource.getType() != IResource.FILE) {
882 			return null;
883 		}
884 
885 		if (cachedContentDescription == null) {
886 			try {
887 				cachedContentDescription = ((IFile) resource)
888 						.getContentDescription();
889 			} catch (CoreException e) {
890 				// silently ignore
891 			}
892 		}
893 		return cachedContentDescription;
894 	}
895 
896 	/*
897 	 * @see PreferencePage#performDefaults()
898 	 */
899 	@Override
performDefaults()900 	protected void performDefaults() {
901 
902 		IResource resource = Adapters.adapt(getElement(), IResource.class);
903 
904 		if (resource == null)
905 			return;
906 
907 		if (newResourceLocation != null) {
908 			newResourceLocation = null;
909 
910 			resolvedLocationValue.setText(IDEResourceInfoUtils
911 					.getResolvedLocationText(resource));
912 
913 			String locationStr = TextProcessor.process(IDEResourceInfoUtils
914 					.getLocationText(resource));
915 			locationValue.setText(locationStr);
916 
917 			if (sizeValue != null)
918 				sizeValue.setText(IDEResourceInfoUtils.getSizeString(resource));
919 		}
920 
921 		// Nothing to update if we never made the box
922 		if (this.editableBox != null) {
923 			this.editableBox.setSelection(false);
924 		}
925 
926 		// Nothing to update if we never made the box
927 		if (this.executableBox != null) {
928 			this.executableBox.setSelection(false);
929 		}
930 
931 		// Nothing to update if we never made the box
932 		if (this.archiveBox != null) {
933 			this.archiveBox.setSelection(true);
934 		}
935 
936 		// Nothing to update if we never made the box
937 		if (this.immutableBox != null) {
938 			this.immutableBox.setSelection(false);
939 		}
940 
941 		// Nothing to update if we never made the box
942 		if (this.derivedBox != null) {
943 			this.derivedBox.setSelection(false);
944 		}
945 
946 		if (permissionBoxes != null) {
947 			int defaultPermissionValues = getDefaulPermissions(resource.getType() == IResource.FOLDER);
948 			setPermissionsSelection(defaultPermissionValues);
949 		}
950 
951 		if (encodingEditor != null) {
952 			encodingEditor.loadDefault();
953 		}
954 
955 		if (lineDelimiterEditor != null) {
956 			lineDelimiterEditor.loadDefault();
957 		}
958 
959 	}
960 
getSimpleChangeName(boolean isSet, String name)961 	private String getSimpleChangeName(boolean isSet, String name) {
962 		String message = "\t"; //$NON-NLS-1$
963 		message += isSet ? IDEWorkbenchMessages.ResourceInfo_recursiveChangesSet
964 				: IDEWorkbenchMessages.ResourceInfo_recursiveChangesUnset;
965 		message += " " + name + "\n"; //$NON-NLS-1$ //$NON-NLS-2$
966 		return message;
967 	}
968 
getAttributesChange(final boolean changedAttrs[], final boolean finalAttrs[])969 	private IResourceChange getAttributesChange(final boolean changedAttrs[],
970 			final boolean finalAttrs[]) {
971 		return new IResourceChange() {
972 			@Override
973 			public String getMessage() {
974 				String message = ""; //$NON-NLS-1$
975 				if (changedAttrs[0])
976 					message += getSimpleChangeName(finalAttrs[0],
977 							IDEWorkbenchMessages.ResourceInfo_readOnly);
978 				if (changedAttrs[1])
979 					message += getSimpleChangeName(finalAttrs[1],
980 							IDEWorkbenchMessages.ResourceInfo_executable);
981 				if (changedAttrs[2])
982 					message += getSimpleChangeName(finalAttrs[2],
983 							IDEWorkbenchMessages.ResourceInfo_archive);
984 				return message;
985 			}
986 
987 			@Override
988 			public void performChange(IResource resource) throws CoreException {
989 				ResourceAttributes attrs = resource.getResourceAttributes();
990 				if (attrs != null) {
991 					if (changedAttrs[0])
992 						attrs.setReadOnly(finalAttrs[0]);
993 					if (changedAttrs[1])
994 						attrs.setExecutable(finalAttrs[1]);
995 					if (changedAttrs[2])
996 						attrs.setArchive(finalAttrs[2]);
997 					resource.setResourceAttributes(attrs);
998 				}
999 			}
1000 		};
1001 	}
1002 
1003 	private IResourceChange getPermissionsChange(final int changedPermissions,
1004 			final int finalPermissions) {
1005 		return new IResourceChange() {
1006 			@Override
1007 			public String getMessage() {
1008 				// iterated with [j][i]
1009 				int permissionMasks[][] = new int[][] {
1010 						{ EFS.ATTRIBUTE_OWNER_READ, EFS.ATTRIBUTE_OWNER_WRITE,
1011 								EFS.ATTRIBUTE_OWNER_EXECUTE },
1012 						{ EFS.ATTRIBUTE_GROUP_READ, EFS.ATTRIBUTE_GROUP_WRITE,
1013 								EFS.ATTRIBUTE_GROUP_EXECUTE },
1014 						{ EFS.ATTRIBUTE_OTHER_READ, EFS.ATTRIBUTE_OTHER_WRITE,
1015 								EFS.ATTRIBUTE_OTHER_EXECUTE } };
1016 				// iterated with [j]
1017 				String groupNames[] = new String[] {
1018 						IDEWorkbenchMessages.ResourceInfo_owner,
1019 						IDEWorkbenchMessages.ResourceInfo_group,
1020 						IDEWorkbenchMessages.ResourceInfo_other };
1021 				// iterated with [i]
1022 				String permissionNames[] = new String[] {
1023 						IDEWorkbenchMessages.ResourceInfo_read,
1024 						IDEWorkbenchMessages.ResourceInfo_write,
1025 						IDEWorkbenchMessages.ResourceInfo_execute };
1026 
1027 				StringBuilder message = new StringBuilder(""); //$NON-NLS-1$
1028 				if ((changedPermissions & EFS.ATTRIBUTE_IMMUTABLE) != 0)
1029 					message.append(getSimpleChangeName(
1030 							(finalPermissions & EFS.ATTRIBUTE_IMMUTABLE) != 0,
1031 							IDEWorkbenchMessages.ResourceInfo_locked));
1032 
1033 				for (int j = 0; j < 3; j++) {
1034 					for (int i = 0; i < 3; i++) {
1035 						if ((changedPermissions & permissionMasks[j][i]) != 0)
1036 							message.append(getSimpleChangeName(
1037 									(finalPermissions & permissionMasks[j][i]) != 0,
1038 									groupNames[j] + " " + permissionNames[i])); //$NON-NLS-1$
1039 					}
1040 				}
1041 				return message.toString();
1042 			}
1043 
1044 			@Override
1045 			public void performChange(IResource resource) {
1046 				int permissions = fetchPermissions(resource);
1047 				// add permissions
1048 				permissions |= changedPermissions & finalPermissions;
1049 				// remove permissions
1050 				permissions &= ~changedPermissions | finalPermissions;
1051 				putPermissions(resource, permissions);
1052 			}
1053 		};
1054 	}
1055 
1056 	private List/*<IResource>*/ getResourcesToVisit(IResource resource) throws CoreException {
1057 		// use set for fast lookup
1058 		final Set/*<URI>*/ visited = new HashSet/*<URI>*/();
1059 		// use list to preserve the order of visited resources
1060 		final List/*<IResource>*/ toVisit = new ArrayList/*<IResource>*/();
1061 		visited.add(resource.getLocationURI());
1062 		resource.accept(proxy -> {
1063 			IResource childResource = proxy.requestResource();
1064 			URI uri = childResource.getLocationURI();
1065 			if (!visited.contains(uri)) {
1066 				visited.add(uri);
1067 				toVisit.add(childResource);
1068 			}
1069 			return true;
1070 		}, IResource.NONE);
1071 		return toVisit;
1072 	}
1073 
1074 	private boolean shouldPerformRecursiveChanges(List/*<IResourceChange>*/ changes) {
1075 		if (!changes.isEmpty()) {
1076 			StringBuilder message = new StringBuilder(IDEWorkbenchMessages.ResourceInfo_recursiveChangesSummary)
1077 					.append('\n');
1078 			for (Object change : changes) {
1079 				message.append(((IResourceChange) change).getMessage());
1080 			}
1081 			message.append(IDEWorkbenchMessages.ResourceInfo_recursiveChangesQuestion);
1082 
1083 			MessageDialog dialog = new MessageDialog(getShell(),
1084 					IDEWorkbenchMessages.ResourceInfo_recursiveChangesTitle,
1085 					null, message.toString(), MessageDialog.QUESTION, 1,
1086 					IDialogConstants.YES_LABEL,
1087 					IDialogConstants.NO_LABEL);
1088 
1089 			return dialog.open() == 0;
1090 		}
1091 		return false;
1092 	}
1093 
1094 	private void scheduleRecursiveChangesJob(final IResource resource, final List/*<IResourceChange>*/ changes) {
1095 		Job.create(IDEWorkbenchMessages.ResourceInfo_recursiveChangesJobName, monitor -> {
1096 			try {
1097 				List/*<IResource>*/ toVisit = getResourcesToVisit(resource);
1098 
1099 				// Prepare the monitor for the given amount of work
1100 				SubMonitor subMonitor = SubMonitor.convert(monitor,
1101 						IDEWorkbenchMessages.ResourceInfo_recursiveChangesJobName,
1102 						toVisit.size());
1103 
1104 				// Apply changes recursively
1105 				for (Iterator/*<IResource>*/ it = toVisit.iterator(); it.hasNext();) {
1106 					SubMonitor iterationMonitor = subMonitor.split(1).setWorkRemaining(changes.size());
1107 					IResource childResource = (IResource) it.next();
1108 					iterationMonitor.subTask(NLS
1109 							.bind(IDEWorkbenchMessages.ResourceInfo_recursiveChangesSubTaskName,
1110 									childResource.getFullPath()));
1111 					for (Object change : changes) {
1112 						iterationMonitor.split(1);
1113 						((IResourceChange) change)
1114 								.performChange(childResource);
1115 					}
1116 				}
1117 			} catch (CoreException e1) {
1118 				IDEWorkbenchPlugin
1119 						.log(IDEWorkbenchMessages.ResourceInfo_recursiveChangesError,
1120 								e1.getStatus());
1121 				return e1.getStatus();
1122 			} catch (OperationCanceledException e2) {
1123 				return Status.CANCEL_STATUS;
1124 			}
1125 			return Status.OK_STATUS;
1126 		}).schedule();
1127 	}
1128 
1129 	/**
1130 	 * Apply the read only state and the encoding to the resource.
1131 	 */
1132 	@Override
1133 	public boolean performOk() {
1134 
1135 		IResource resource = Adapters.adapt(getElement(), IResource.class);
1136 
1137 		if (resource == null)
1138 			return true;
1139 
1140 		if (lineDelimiterEditor != null) {
1141 			lineDelimiterEditor.store();
1142 		}
1143 
1144 		try {
1145 			if (newResourceLocation != null) {
1146 				if (resource.getType() == IResource.FILE)
1147 					((IFile)resource).createLink(newResourceLocation, IResource.REPLACE,
1148 							new NullProgressMonitor());
1149 				if (resource.getType() == IResource.FOLDER)
1150 					((IFolder)resource).createLink(newResourceLocation, IResource.REPLACE,
1151 							new NullProgressMonitor());
1152 			}
1153 
1154 			List/*<IResourceChange>*/ changes = new ArrayList/*<IResourceChange>*/();
1155 
1156 			ResourceAttributes attrs = resource.getResourceAttributes();
1157 			if (attrs != null) {
1158 				boolean finalValues[] = new boolean[] { false, false, false };
1159 				boolean changedAttrs[] = new boolean[] { false, false, false };
1160 				// Nothing to update if we never made the box
1161 				if (editableBox != null
1162 						&& editableBox.getSelection() != previousReadOnlyValue) {
1163 					attrs.setReadOnly(editableBox.getSelection());
1164 					finalValues[0] = editableBox.getSelection();
1165 					changedAttrs[0] = true;
1166 				}
1167 				if (executableBox != null
1168 						&& executableBox.getSelection() != previousExecutableValue) {
1169 					attrs.setExecutable(executableBox.getSelection());
1170 					finalValues[1] = executableBox.getSelection();
1171 					changedAttrs[1] = true;
1172 				}
1173 				if (archiveBox != null
1174 						&& archiveBox.getSelection() != previousArchiveValue) {
1175 					attrs.setArchive(archiveBox.getSelection());
1176 					finalValues[2] = archiveBox.getSelection();
1177 					changedAttrs[2] = true;
1178 				}
1179 				if (changedAttrs[0] || changedAttrs[1] || changedAttrs[2]) {
1180 					resource.setResourceAttributes(attrs);
1181 					attrs = resource.getResourceAttributes();
1182 					if (attrs != null) {
1183 						previousReadOnlyValue = attrs.isReadOnly();
1184 						previousExecutableValue = attrs.isExecutable();
1185 						previousArchiveValue = attrs.isArchive();
1186 						if (editableBox != null) {
1187 							editableBox.setSelection(attrs.isReadOnly());
1188 						}
1189 						if (executableBox != null) {
1190 							executableBox.setSelection(attrs.isExecutable());
1191 						}
1192 						if (archiveBox != null) {
1193 							archiveBox.setSelection(attrs.isArchive());
1194 						}
1195 						if (resource.getType() == IResource.FOLDER) {
1196 							changes.add(getAttributesChange(changedAttrs,
1197 									finalValues));
1198 						}
1199 					}
1200 				}
1201 			}
1202 
1203 			if (permissionBoxes != null) {
1204 				int permissionValues = getPermissionsSelection();
1205 				if (previousPermissionsValue != permissionValues) {
1206 					int changedPermissions = previousPermissionsValue ^ permissionValues;
1207 					putPermissions(resource, permissionValues);
1208 					previousPermissionsValue = fetchPermissions(resource);
1209 					if (previousPermissionsValue != permissionValues) {
1210 						// We failed to set some of the permissions
1211 						setPermissionsSelection(previousPermissionsValue);
1212 					}
1213 					if (resource.getType() == IResource.FOLDER) {
1214 						changes.add(getPermissionsChange(changedPermissions,
1215 								permissionValues));
1216 					}
1217 				}
1218 			}
1219 
1220 			if (shouldPerformRecursiveChanges(changes))
1221 				scheduleRecursiveChangesJob(resource, changes);
1222 
1223 			// Nothing to update if we never made the box
1224 			if (this.derivedBox != null) {
1225 				boolean localDerivedValue = derivedBox.getSelection();
1226 				if (previousDerivedValue != localDerivedValue) {
1227 					resource.setDerived(localDerivedValue, null);
1228 					boolean isDerived = resource.isDerived();
1229 					previousDerivedValue = isDerived;
1230 					derivedBox.setSelection(isDerived);
1231 				}
1232 			}
1233 		} catch (CoreException exception) {
1234 			ErrorDialog.openError(getShell(),
1235 					IDEWorkbenchMessages.InternalError, exception
1236 							.getLocalizedMessage(), exception.getStatus());
1237 			return false;
1238 		} finally {
1239 			// This must be invoked after the 'derived' property has been set,
1240 			// because it may influence the place where encoding is stored.
1241 			if (encodingEditor != null) {
1242 				encodingEditor.store();
1243 			}
1244 		}
1245 		return true;
1246 	}
1247 }
1248