001: /*******************************************************************************
002: * Copyright (c) 2000, 2006 IBM Corporation and others.
003: * All rights reserved. This program and the accompanying materials
004: * are made available under the terms of the Eclipse Public License v1.0
005: * which accompanies this distribution, and is available at
006: * http://www.eclipse.org/legal/epl-v10.html
007: *
008: * Contributors:
009: * IBM Corporation - initial API and implementation
010: *******************************************************************************/package org.eclipse.jdt.ui.wizards;
011:
012: import java.net.URL;
013: import java.util.ArrayList;
014:
015: import org.eclipse.core.runtime.IPath;
016: import org.eclipse.core.runtime.Path;
017:
018: import org.eclipse.core.resources.IContainer;
019: import org.eclipse.core.resources.IFile;
020: import org.eclipse.core.resources.IFolder;
021: import org.eclipse.core.resources.IProject;
022: import org.eclipse.core.resources.IResource;
023: import org.eclipse.core.resources.IWorkspaceRoot;
024: import org.eclipse.core.resources.ResourcesPlugin;
025:
026: import org.eclipse.swt.SWT;
027: import org.eclipse.swt.widgets.FileDialog;
028: import org.eclipse.swt.widgets.Shell;
029:
030: import org.eclipse.jface.window.Window;
031:
032: import org.eclipse.ui.dialogs.ElementTreeSelectionDialog;
033: import org.eclipse.ui.model.WorkbenchContentProvider;
034: import org.eclipse.ui.model.WorkbenchLabelProvider;
035:
036: import org.eclipse.ui.views.navigator.ResourceComparator;
037:
038: import org.eclipse.jdt.core.IClasspathEntry;
039: import org.eclipse.jdt.core.IJavaProject;
040:
041: import org.eclipse.jdt.ui.JavaUI;
042:
043: import org.eclipse.jdt.internal.ui.IUIConstants;
044: import org.eclipse.jdt.internal.ui.JavaPlugin;
045: import org.eclipse.jdt.internal.ui.wizards.NewWizardMessages;
046: import org.eclipse.jdt.internal.ui.wizards.TypedElementSelectionValidator;
047: import org.eclipse.jdt.internal.ui.wizards.TypedViewerFilter;
048: import org.eclipse.jdt.internal.ui.wizards.buildpaths.ArchiveFileFilter;
049: import org.eclipse.jdt.internal.ui.wizards.buildpaths.CPListElement;
050: import org.eclipse.jdt.internal.ui.wizards.buildpaths.ClasspathContainerWizard;
051: import org.eclipse.jdt.internal.ui.wizards.buildpaths.EditVariableEntryDialog;
052: import org.eclipse.jdt.internal.ui.wizards.buildpaths.JavadocLocationDialog;
053: import org.eclipse.jdt.internal.ui.wizards.buildpaths.MultipleFolderSelectionDialog;
054: import org.eclipse.jdt.internal.ui.wizards.buildpaths.NewVariableEntryDialog;
055: import org.eclipse.jdt.internal.ui.wizards.buildpaths.SourceAttachmentDialog;
056:
057: /**
058: * Class that gives access to dialogs used by the Java build path page to configure classpath entries
059: * and properties of classpath entries.
060: * Static methods are provided to show dialogs for:
061: * <ul>
062: * <li> configuration of source attachments</li>
063: * <li> configuration of Javadoc locations</li>
064: * <li> configuration and selection of classpath variable entries</li>
065: * <li> configuration and selection of classpath container entries</li>
066: * <li> configuration and selection of JAR and external JAR entries</li>
067: * <li> selection of class and source folders</li>
068: * </ul>
069: * <p>
070: * This class is not intended to be instantiated or subclassed by clients.
071: * </p>
072: * @since 3.0
073: */
074: public final class BuildPathDialogAccess {
075:
076: private BuildPathDialogAccess() {
077: // do not instantiate
078: }
079:
080: /**
081: * Shows the UI for configuring source attachments. <code>null</code> is returned
082: * if the user cancels the dialog. The dialog does not apply any changes.
083: *
084: * @param shell The parent shell for the dialog
085: * @param initialEntry The entry to edit. The kind of the classpath entry must be either
086: * <code>IClasspathEntry.CPE_LIBRARY</code> or <code>IClasspathEntry.CPE_VARIABLE</code>.
087: * @return Returns the resulting classpath entry containing a potentially modified source attachment path and
088: * source attachment root. The resulting entry can be used to replace the original entry on the classpath.
089: * Note that the dialog does not make any changes on the passed entry nor on the classpath that
090: * contains it.
091: */
092: public static IClasspathEntry configureSourceAttachment(
093: Shell shell, IClasspathEntry initialEntry) {
094: if (initialEntry == null) {
095: throw new IllegalArgumentException();
096: }
097: int entryKind = initialEntry.getEntryKind();
098: if (entryKind != IClasspathEntry.CPE_LIBRARY
099: && entryKind != IClasspathEntry.CPE_VARIABLE) {
100: throw new IllegalArgumentException();
101: }
102:
103: SourceAttachmentDialog dialog = new SourceAttachmentDialog(
104: shell, initialEntry);
105: if (dialog.open() == Window.OK) {
106: return dialog.getResult();
107: }
108: return null;
109: }
110:
111: /**
112: * Shows the UI for configuring a javadoc location. <code>null</code> is returned
113: * if the user cancels the dialog. If OK is pressed, an array of length 1 containing the configured URL is
114: * returned. Note that the configured URL can be <code>null</code> when the user
115: * wishes to have no URL location specified. The dialog does not apply any changes.
116: * Use {@link org.eclipse.jdt.ui.JavaUI} to access and configure
117: * Javadoc locations.
118: *
119: * @param shell The parent shell for the dialog.
120: * @param libraryName Name of of the library to which configured javadoc location belongs.
121: * @param initialURL The initial URL or <code>null</code>.
122: * @return Returns an array of size 1 that contains the resulting javadoc location or
123: * <code>null</code> if the dialog has been canceled. Note that the configured URL can be <code>null</code> when the user
124: * wishes to have no URL location specified.
125: */
126: public static URL[] configureJavadocLocation(Shell shell,
127: String libraryName, URL initialURL) {
128: if (libraryName == null) {
129: throw new IllegalArgumentException();
130: }
131:
132: JavadocLocationDialog dialog = new JavadocLocationDialog(shell,
133: libraryName, initialURL);
134: if (dialog.open() == Window.OK) {
135: return new URL[] { dialog.getResult() };
136: }
137: return null;
138: }
139:
140: /**
141: * Shows the UI for configuring a javadoc location attribute of the classpath entry. <code>null</code> is returned
142: * if the user cancels the dialog. The dialog does not apply any changes.
143: *
144: * @param shell The parent shell for the dialog.
145: * @param initialEntry The entry to edit. The kind of the classpath entry must be either
146: * <code>IClasspathEntry.CPE_LIBRARY</code> or <code>IClasspathEntry.CPE_VARIABLE</code>.
147: * @return Returns the resulting classpath entry containing a potentially modified javadoc location attribute
148: * The resulting entry can be used to replace the original entry on the classpath.
149: * Note that the dialog does not make any changes on the passed entry nor on the classpath that
150: * contains it.
151: *
152: * @since 3.1
153: */
154: public static IClasspathEntry configureJavadocLocation(Shell shell,
155: IClasspathEntry initialEntry) {
156: if (initialEntry == null) {
157: throw new IllegalArgumentException();
158: }
159: int entryKind = initialEntry.getEntryKind();
160: if (entryKind != IClasspathEntry.CPE_LIBRARY
161: && entryKind != IClasspathEntry.CPE_VARIABLE) {
162: throw new IllegalArgumentException();
163: }
164:
165: URL location = JavaUI.getLibraryJavadocLocation(initialEntry);
166: JavadocLocationDialog dialog = new JavadocLocationDialog(shell,
167: initialEntry.getPath().toString(), location);
168: if (dialog.open() == Window.OK) {
169: CPListElement element = CPListElement.createFromExisting(
170: initialEntry, null);
171: URL res = dialog.getResult();
172: element.setAttribute(CPListElement.JAVADOC,
173: res != null ? res.toExternalForm() : null);
174: return element.getClasspathEntry();
175: }
176: return null;
177: }
178:
179: /**
180: * Shows the UI for configuring a variable classpath entry. See {@link IClasspathEntry#CPE_VARIABLE} for
181: * details about variable classpath entries.
182: * The dialog returns the configured classpath entry path or <code>null</code> if the dialog has
183: * been canceled. The dialog does not apply any changes.
184: *
185: * @param shell The parent shell for the dialog.
186: * @param initialEntryPath The initial variable classpath variable path or <code>null</code> to use
187: * an empty path.
188: * @param existingPaths An array of paths that are already on the classpath and therefore should not be
189: * selected again.
190: * @return Returns the configures classpath entry path or <code>null</code> if the dialog has
191: * been canceled.
192: */
193: public static IPath configureVariableEntry(Shell shell,
194: IPath initialEntryPath, IPath[] existingPaths) {
195: if (existingPaths == null) {
196: throw new IllegalArgumentException();
197: }
198:
199: EditVariableEntryDialog dialog = new EditVariableEntryDialog(
200: shell, initialEntryPath, existingPaths);
201: if (dialog.open() == Window.OK) {
202: return dialog.getPath();
203: }
204: return null;
205: }
206:
207: /**
208: * Shows the UI for selecting new variable classpath entries. See {@link IClasspathEntry#CPE_VARIABLE} for
209: * details about variable classpath entries.
210: * The dialog returns an array of the selected variable entries or <code>null</code> if the dialog has
211: * been canceled. The dialog does not apply any changes.
212: *
213: * @param shell The parent shell for the dialog.
214: * @param existingPaths An array of paths that are already on the classpath and therefore should not be
215: * selected again.
216: * @return Returns an non empty array of the selected variable entries or <code>null</code> if the dialog has
217: * been canceled.
218: */
219: public static IPath[] chooseVariableEntries(Shell shell,
220: IPath[] existingPaths) {
221: if (existingPaths == null) {
222: throw new IllegalArgumentException();
223: }
224: NewVariableEntryDialog dialog = new NewVariableEntryDialog(
225: shell);
226: if (dialog.open() == Window.OK) {
227: return dialog.getResult();
228: }
229: return null;
230: }
231:
232: /**
233: * Shows the UI to configure a classpath container classpath entry. See {@link IClasspathEntry#CPE_CONTAINER} for
234: * details about container classpath entries.
235: * The dialog returns the configured classpath entry or <code>null</code> if the dialog has
236: * been canceled. The dialog does not apply any changes.
237: *
238: * @param shell The parent shell for the dialog.
239: * @param initialEntry The initial classpath container entry.
240: * @param project The project the entry belongs to. The project does not have to exist and can also be <code>null</code>.
241: * @param currentClasspath The class path entries currently selected to be set as the projects classpath. This can also
242: * include the entry to be edited. The dialog uses these entries as information only (e.g. to avoid duplicate entries); The user still can make changes after the
243: * the classpath container dialog has been closed. See {@link IClasspathContainerPageExtension} for
244: * more information.
245: * @return Returns the configured classpath container entry or <code>null</code> if the dialog has
246: * been canceled by the user.
247: */
248: public static IClasspathEntry configureContainerEntry(Shell shell,
249: IClasspathEntry initialEntry, IJavaProject project,
250: IClasspathEntry[] currentClasspath) {
251: if (initialEntry == null || currentClasspath == null) {
252: throw new IllegalArgumentException();
253: }
254:
255: ClasspathContainerWizard wizard = new ClasspathContainerWizard(
256: initialEntry, project, currentClasspath);
257: if (ClasspathContainerWizard.openWizard(shell, wizard) == Window.OK) {
258: IClasspathEntry[] created = wizard.getNewEntries();
259: if (created != null && created.length == 1) {
260: return created[0];
261: }
262: }
263: return null;
264: }
265:
266: /**
267: * Shows the UI to choose new classpath container classpath entries. See {@link IClasspathEntry#CPE_CONTAINER} for
268: * details about container classpath entries.
269: * The dialog returns the selected classpath entries or <code>null</code> if the dialog has
270: * been canceled. The dialog does not apply any changes.
271: *
272: * @param shell The parent shell for the dialog.
273: * @param project The project the entry belongs to. The project does not have to exist and
274: * can also be <code>null</code>.
275: * @param currentClasspath The class path entries currently selected to be set as the projects classpath. This can also
276: * include the entry to be edited. The dialog uses these entries as information only; The user still can make changes after the
277: * the classpath container dialog has been closed. See {@link IClasspathContainerPageExtension} for
278: * more information.
279: * @return Returns the selected classpath container entries or <code>null</code> if the dialog has
280: * been canceled by the user.
281: */
282: public static IClasspathEntry[] chooseContainerEntries(Shell shell,
283: IJavaProject project, IClasspathEntry[] currentClasspath) {
284: if (currentClasspath == null) {
285: throw new IllegalArgumentException();
286: }
287:
288: ClasspathContainerWizard wizard = new ClasspathContainerWizard(
289: (IClasspathEntry) null, project, currentClasspath);
290: if (ClasspathContainerWizard.openWizard(shell, wizard) == Window.OK) {
291: return wizard.getNewEntries();
292: }
293: return null;
294: }
295:
296: /**
297: * Shows the UI to configure a JAR or ZIP archive located in the workspace.
298: * The dialog returns the configured classpath entry path or <code>null</code> if the dialog has
299: * been canceled. The dialog does not apply any changes.
300: *
301: * @param shell The parent shell for the dialog.
302: * @param initialEntry The path of the initial archive entry
303: * @param usedEntries An array of paths that are already on the classpath and therefore should not be
304: * selected again.
305: * @return Returns the configured classpath container entry path or <code>null</code> if the dialog has
306: * been canceled by the user.
307: */
308: public static IPath configureJAREntry(Shell shell,
309: IPath initialEntry, IPath[] usedEntries) {
310: if (initialEntry == null || usedEntries == null) {
311: throw new IllegalArgumentException();
312: }
313:
314: Class[] acceptedClasses = new Class[] { IFile.class };
315: TypedElementSelectionValidator validator = new TypedElementSelectionValidator(
316: acceptedClasses, false);
317:
318: ArrayList usedJars = new ArrayList(usedEntries.length);
319: IWorkspaceRoot root = ResourcesPlugin.getWorkspace().getRoot();
320: for (int i = 0; i < usedEntries.length; i++) {
321: IPath curr = usedEntries[i];
322: if (!curr.equals(initialEntry)) {
323: IResource resource = root.findMember(usedEntries[i]);
324: if (resource instanceof IFile) {
325: usedJars.add(resource);
326: }
327: }
328: }
329:
330: IResource existing = root.findMember(initialEntry);
331:
332: ElementTreeSelectionDialog dialog = new ElementTreeSelectionDialog(
333: shell, new WorkbenchLabelProvider(),
334: new WorkbenchContentProvider());
335: dialog.setValidator(validator);
336: dialog
337: .setTitle(NewWizardMessages.BuildPathDialogAccess_JARArchiveDialog_edit_title);
338: dialog
339: .setMessage(NewWizardMessages.BuildPathDialogAccess_JARArchiveDialog_edit_description);
340: dialog.addFilter(new ArchiveFileFilter(usedJars, true));
341: dialog.setInput(root);
342: dialog.setComparator(new ResourceComparator(
343: ResourceComparator.NAME));
344: dialog.setInitialSelection(existing);
345:
346: if (dialog.open() == Window.OK) {
347: IResource element = (IResource) dialog.getFirstResult();
348: return element.getFullPath();
349: }
350: return null;
351: }
352:
353: /**
354: * Shows the UI to select new JAR or ZIP archive entries located in the workspace.
355: * The dialog returns the selected entries or <code>null</code> if the dialog has
356: * been canceled. The dialog does not apply any changes.
357: *
358: * @param shell The parent shell for the dialog.
359: * @param initialSelection The path of the element (container or archive) to initially select or <code>null</code> to not select an entry.
360: * @param usedEntries An array of paths that are already on the classpath and therefore should not be
361: * selected again.
362: * @return Returns the new classpath container entry paths or <code>null</code> if the dialog has
363: * been canceled by the user.
364: */
365: public static IPath[] chooseJAREntries(Shell shell,
366: IPath initialSelection, IPath[] usedEntries) {
367: if (usedEntries == null) {
368: throw new IllegalArgumentException();
369: }
370:
371: Class[] acceptedClasses = new Class[] { IFile.class };
372: TypedElementSelectionValidator validator = new TypedElementSelectionValidator(
373: acceptedClasses, true);
374: ArrayList usedJars = new ArrayList(usedEntries.length);
375: IWorkspaceRoot root = ResourcesPlugin.getWorkspace().getRoot();
376: for (int i = 0; i < usedEntries.length; i++) {
377: IResource resource = root.findMember(usedEntries[i]);
378: if (resource instanceof IFile) {
379: usedJars.add(resource);
380: }
381: }
382: IResource focus = initialSelection != null ? root
383: .findMember(initialSelection) : null;
384:
385: ElementTreeSelectionDialog dialog = new ElementTreeSelectionDialog(
386: shell, new WorkbenchLabelProvider(),
387: new WorkbenchContentProvider());
388: dialog.setHelpAvailable(false);
389: dialog.setValidator(validator);
390: dialog
391: .setTitle(NewWizardMessages.BuildPathDialogAccess_JARArchiveDialog_new_title);
392: dialog
393: .setMessage(NewWizardMessages.BuildPathDialogAccess_JARArchiveDialog_new_description);
394: dialog.addFilter(new ArchiveFileFilter(usedJars, true));
395: dialog.setInput(root);
396: dialog.setComparator(new ResourceComparator(
397: ResourceComparator.NAME));
398: dialog.setInitialSelection(focus);
399:
400: if (dialog.open() == Window.OK) {
401: Object[] elements = dialog.getResult();
402: IPath[] res = new IPath[elements.length];
403: for (int i = 0; i < res.length; i++) {
404: IResource elem = (IResource) elements[i];
405: res[i] = elem.getFullPath();
406: }
407: return res;
408: }
409: return null;
410: }
411:
412: /**
413: * Shows the UI to configure an external JAR or ZIP archive.
414: * The dialog returns the configured or <code>null</code> if the dialog has
415: * been canceled. The dialog does not apply any changes.
416: *
417: * @param shell The parent shell for the dialog.
418: * @param initialEntry The path of the initial archive entry.
419: * @return Returns the configured classpath container entry path or <code>null</code> if the dialog has
420: * been canceled by the user.
421: */
422: public static IPath configureExternalJAREntry(Shell shell,
423: IPath initialEntry) {
424: if (initialEntry == null) {
425: throw new IllegalArgumentException();
426: }
427:
428: String lastUsedPath = initialEntry.removeLastSegments(1)
429: .toOSString();
430:
431: FileDialog dialog = new FileDialog(shell, SWT.SINGLE);
432: dialog
433: .setText(NewWizardMessages.BuildPathDialogAccess_ExtJARArchiveDialog_edit_title);
434: dialog.setFilterExtensions(ArchiveFileFilter.FILTER_EXTENSIONS);
435: dialog.setFilterPath(lastUsedPath);
436: dialog.setFileName(initialEntry.lastSegment());
437:
438: String res = dialog.open();
439: if (res == null) {
440: return null;
441: }
442: JavaPlugin.getDefault().getDialogSettings().put(
443: IUIConstants.DIALOGSTORE_LASTEXTJAR,
444: dialog.getFilterPath());
445:
446: return Path.fromOSString(res).makeAbsolute();
447: }
448:
449: /**
450: * Shows the UI to select new external JAR or ZIP archive entries.
451: * The dialog returns the selected entry paths or <code>null</code> if the dialog has
452: * been canceled. The dialog does not apply any changes.
453: *
454: * @param shell The parent shell for the dialog.
455: * @return Returns the new classpath container entry paths or <code>null</code> if the dialog has
456: * been canceled by the user.
457: */
458: public static IPath[] chooseExternalJAREntries(Shell shell) {
459: String lastUsedPath = JavaPlugin.getDefault()
460: .getDialogSettings().get(
461: IUIConstants.DIALOGSTORE_LASTEXTJAR);
462: if (lastUsedPath == null) {
463: lastUsedPath = ""; //$NON-NLS-1$
464: }
465: FileDialog dialog = new FileDialog(shell, SWT.MULTI);
466: dialog
467: .setText(NewWizardMessages.BuildPathDialogAccess_ExtJARArchiveDialog_new_title);
468: dialog.setFilterExtensions(ArchiveFileFilter.FILTER_EXTENSIONS);
469: dialog.setFilterPath(lastUsedPath);
470:
471: String res = dialog.open();
472: if (res == null) {
473: return null;
474: }
475: String[] fileNames = dialog.getFileNames();
476: int nChosen = fileNames.length;
477:
478: IPath filterPath = Path.fromOSString(dialog.getFilterPath());
479: IPath[] elems = new IPath[nChosen];
480: for (int i = 0; i < nChosen; i++) {
481: elems[i] = filterPath.append(fileNames[i]).makeAbsolute();
482: }
483: JavaPlugin.getDefault().getDialogSettings().put(
484: IUIConstants.DIALOGSTORE_LASTEXTJAR,
485: dialog.getFilterPath());
486:
487: return elems;
488: }
489:
490: /**
491: * Shows the UI to select new class folders.
492: * The dialog returns the selected classpath entry paths or <code>null</code> if the dialog has
493: * been canceled. The dialog does not apply any changes.
494: *
495: * @param shell The parent shell for the dialog.
496: * @param initialSelection The path of the element to initially select or <code>null</code>.
497: * @param usedEntries An array of paths that are already on the classpath and therefore should not be
498: * selected again.
499: * @return Returns the configured classpath container entry path or <code>null</code> if the dialog has
500: * been canceled by the user.
501: */
502: public static IPath[] chooseClassFolderEntries(Shell shell,
503: IPath initialSelection, IPath[] usedEntries) {
504: if (usedEntries == null) {
505: throw new IllegalArgumentException();
506: }
507: String title = NewWizardMessages.BuildPathDialogAccess_ExistingClassFolderDialog_new_title;
508: String message = NewWizardMessages.BuildPathDialogAccess_ExistingClassFolderDialog_new_description;
509: return internalChooseFolderEntry(shell, initialSelection,
510: usedEntries, title, message);
511: }
512:
513: /**
514: * Shows the UI to select new source folders.
515: * The dialog returns the selected classpath entry paths or <code>null</code> if the dialog has
516: * been canceled The dialog does not apply any changes.
517: *
518: * @param shell The parent shell for the dialog.
519: * @param initialSelection The path of the element to initially select or <code>null</code>
520: * @param usedEntries An array of paths that are already on the classpath and therefore should not be
521: * selected again.
522: * @return Returns the configured classpath container entry path or <code>null</code> if the dialog has
523: * been canceled by the user.
524: */
525: public static IPath[] chooseSourceFolderEntries(Shell shell,
526: IPath initialSelection, IPath[] usedEntries) {
527: if (usedEntries == null) {
528: throw new IllegalArgumentException();
529: }
530: String title = NewWizardMessages.BuildPathDialogAccess_ExistingSourceFolderDialog_new_title;
531: String message = NewWizardMessages.BuildPathDialogAccess_ExistingSourceFolderDialog_new_description;
532: return internalChooseFolderEntry(shell, initialSelection,
533: usedEntries, title, message);
534: }
535:
536: private static IPath[] internalChooseFolderEntry(Shell shell,
537: IPath initialSelection, IPath[] usedEntries, String title,
538: String message) {
539: Class[] acceptedClasses = new Class[] { IProject.class,
540: IFolder.class };
541:
542: ArrayList usedContainers = new ArrayList(usedEntries.length);
543: IWorkspaceRoot root = ResourcesPlugin.getWorkspace().getRoot();
544: for (int i = 0; i < usedEntries.length; i++) {
545: IResource resource = root.findMember(usedEntries[i]);
546: if (resource instanceof IContainer) {
547: usedContainers.add(resource);
548: }
549: }
550:
551: IResource focus = initialSelection != null ? root
552: .findMember(initialSelection) : null;
553: Object[] used = usedContainers.toArray();
554:
555: MultipleFolderSelectionDialog dialog = new MultipleFolderSelectionDialog(
556: shell, new WorkbenchLabelProvider(),
557: new WorkbenchContentProvider());
558: dialog.setExisting(used);
559: dialog.setTitle(title);
560: dialog.setMessage(message);
561: dialog.setHelpAvailable(false);
562: dialog.addFilter(new TypedViewerFilter(acceptedClasses, used));
563: dialog.setInput(root);
564: dialog.setInitialFocus(focus);
565:
566: if (dialog.open() == Window.OK) {
567: Object[] elements = dialog.getResult();
568: IPath[] res = new IPath[elements.length];
569: for (int i = 0; i < res.length; i++) {
570: IResource elem = (IResource) elements[i];
571: res[i] = elem.getFullPath();
572: }
573: return res;
574: }
575: return null;
576: }
577: }
|