001: /*******************************************************************************
002: * Copyright (c) 2000, 2007 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: * Brock Janiczak <brockj@tpg.com.au> - bug 201044
011: *******************************************************************************/package org.eclipse.pde.internal.ui.wizards.plugin;
012:
013: import java.lang.reflect.InvocationTargetException;
014: import java.util.ArrayList;
015: import java.util.Iterator;
016: import java.util.Set;
017: import java.util.TreeSet;
018:
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.ProjectScope;
023: import org.eclipse.core.runtime.CoreException;
024: import org.eclipse.core.runtime.IPath;
025: import org.eclipse.core.runtime.IProgressMonitor;
026: import org.eclipse.core.runtime.Path;
027: import org.eclipse.core.runtime.SubProgressMonitor;
028: import org.eclipse.core.runtime.preferences.IEclipsePreferences;
029: import org.eclipse.jdt.core.IClasspathEntry;
030: import org.eclipse.jdt.core.IJavaElement;
031: import org.eclipse.jdt.core.IJavaProject;
032: import org.eclipse.jdt.core.IPackageFragment;
033: import org.eclipse.jdt.core.IPackageFragmentRoot;
034: import org.eclipse.jdt.core.JavaCore;
035: import org.eclipse.jdt.core.JavaModelException;
036: import org.eclipse.jface.viewers.ISelection;
037: import org.eclipse.jface.viewers.StructuredSelection;
038: import org.eclipse.pde.core.build.IBuildEntry;
039: import org.eclipse.pde.core.build.IBuildModelFactory;
040: import org.eclipse.pde.core.plugin.IFragment;
041: import org.eclipse.pde.core.plugin.IPlugin;
042: import org.eclipse.pde.core.plugin.IPluginBase;
043: import org.eclipse.pde.core.plugin.IPluginImport;
044: import org.eclipse.pde.core.plugin.IPluginLibrary;
045: import org.eclipse.pde.core.plugin.IPluginReference;
046: import org.eclipse.pde.internal.core.ICoreConstants;
047: import org.eclipse.pde.internal.core.PDECore;
048: import org.eclipse.pde.internal.core.build.WorkspaceBuildModel;
049: import org.eclipse.pde.internal.core.bundle.BundlePluginBase;
050: import org.eclipse.pde.internal.core.bundle.WorkspaceBundleFragmentModel;
051: import org.eclipse.pde.internal.core.bundle.WorkspaceBundlePluginModel;
052: import org.eclipse.pde.internal.core.bundle.WorkspaceBundlePluginModelBase;
053: import org.eclipse.pde.internal.core.ibundle.IBundle;
054: import org.eclipse.pde.internal.core.ibundle.IBundlePluginBase;
055: import org.eclipse.pde.internal.core.ibundle.IBundlePluginModelBase;
056: import org.eclipse.pde.internal.core.natures.PDE;
057: import org.eclipse.pde.internal.core.plugin.WorkspaceFragmentModel;
058: import org.eclipse.pde.internal.core.plugin.WorkspacePluginModel;
059: import org.eclipse.pde.internal.core.plugin.WorkspacePluginModelBase;
060: import org.eclipse.pde.internal.core.util.CoreUtility;
061: import org.eclipse.pde.internal.ui.PDEPlugin;
062: import org.eclipse.pde.internal.ui.PDEUIMessages;
063: import org.eclipse.pde.internal.ui.wizards.IProjectProvider;
064: import org.eclipse.pde.ui.IBundleContentWizard;
065: import org.eclipse.pde.ui.IFieldData;
066: import org.eclipse.pde.ui.IFragmentFieldData;
067: import org.eclipse.pde.ui.IPluginContentWizard;
068: import org.eclipse.pde.ui.IPluginFieldData;
069: import org.eclipse.ui.IWorkbenchPage;
070: import org.eclipse.ui.IWorkbenchPart;
071: import org.eclipse.ui.IWorkbenchWindow;
072: import org.eclipse.ui.PartInitException;
073: import org.eclipse.ui.actions.WorkspaceModifyOperation;
074: import org.eclipse.ui.ide.IDE;
075: import org.eclipse.ui.part.ISetSelectionTarget;
076: import org.osgi.framework.Constants;
077: import org.osgi.service.prefs.BackingStoreException;
078:
079: public class NewProjectCreationOperation extends
080: WorkspaceModifyOperation {
081: private IPluginContentWizard fContentWizard;
082:
083: private IFieldData fData;
084:
085: private PluginClassCodeGenerator fGenerator;
086:
087: private WorkspacePluginModelBase fModel;
088:
089: private IProjectProvider fProjectProvider;
090:
091: private boolean fResult;
092:
093: public NewProjectCreationOperation(IFieldData data,
094: IProjectProvider provider,
095: IPluginContentWizard contentWizard) {
096: fData = data;
097: fProjectProvider = provider;
098: fContentWizard = contentWizard;
099: }
100:
101: // function used to modify Manifest just before it is written out (after all project artifacts have been created.
102: protected void adjustManifests(IProgressMonitor monitor,
103: IProject project, IPluginBase bundle) throws CoreException {
104: // if libraries are exported, compute export package (173393)
105: IPluginLibrary[] libs = fModel.getPluginBase().getLibraries();
106: Set packages = new TreeSet();
107: for (int i = 0; i < libs.length; i++) {
108: String[] filters = libs[i].getContentFilters();
109: // if a library is fully exported, then export all source packages (since we don't know which source folders go with which library)
110: if (filters.length == 1 && filters[0].equals("**")) { //$NON-NLS-1$
111: addAllSourcePackages(project, packages);
112: break;
113: }
114: for (int j = 0; j < filters.length; j++) {
115: if (filters[j].endsWith(".*")) //$NON-NLS-1$
116: packages.add(filters[j].substring(0, filters[j]
117: .length() - 2));
118: }
119: }
120: if (!packages.isEmpty()) {
121: IBundle iBundle = ((WorkspaceBundlePluginModelBase) fModel)
122: .getBundleModel().getBundle();
123: iBundle.setHeader(Constants.EXPORT_PACKAGE,
124: getCommaValueFromSet(packages));
125: }
126: }
127:
128: private void createBuildPropertiesFile(IProject project)
129: throws CoreException {
130: IFile file = project.getFile("build.properties"); //$NON-NLS-1$
131: if (!file.exists()) {
132: WorkspaceBuildModel model = new WorkspaceBuildModel(file);
133: IBuildModelFactory factory = model.getFactory();
134:
135: // BIN.INCLUDES
136: IBuildEntry binEntry = factory
137: .createEntry(IBuildEntry.BIN_INCLUDES);
138: fillBinIncludes(project, binEntry);
139: createSourceOutputBuildEntries(model, factory);
140: model.getBuild().add(binEntry);
141: model.save();
142: }
143: }
144:
145: protected void createSourceOutputBuildEntries(
146: WorkspaceBuildModel model, IBuildModelFactory factory)
147: throws CoreException {
148: String srcFolder = fData.getSourceFolderName();
149: if (!fData.isSimple() && srcFolder != null) {
150: String libraryName = fData.getLibraryName();
151: if (libraryName == null)
152: libraryName = "."; //$NON-NLS-1$
153: // SOURCE.<LIBRARY_NAME>
154: IBuildEntry entry = factory
155: .createEntry(IBuildEntry.JAR_PREFIX + libraryName);
156: if (srcFolder.length() > 0)
157: entry.addToken(new Path(srcFolder)
158: .addTrailingSeparator().toString());
159: else
160: entry.addToken("."); //$NON-NLS-1$
161: model.getBuild().add(entry);
162:
163: // OUTPUT.<LIBRARY_NAME>
164: entry = factory.createEntry(IBuildEntry.OUTPUT_PREFIX
165: + libraryName);
166: String outputFolder = fData.getOutputFolderName().trim();
167: if (outputFolder.length() > 0)
168: entry.addToken(new Path(outputFolder)
169: .addTrailingSeparator().toString());
170: else
171: entry.addToken("."); //$NON-NLS-1$
172: model.getBuild().add(entry);
173: }
174: }
175:
176: protected void createContents(IProgressMonitor monitor,
177: IProject project) throws CoreException, JavaModelException,
178: InvocationTargetException, InterruptedException {
179: }
180:
181: private void createManifest(IProject project) throws CoreException {
182: if (fData.hasBundleStructure()) {
183: if (fData instanceof IFragmentFieldData) {
184: fModel = new WorkspaceBundleFragmentModel(
185: project
186: .getFile(ICoreConstants.BUNDLE_FILENAME_DESCRIPTOR),
187: project
188: .getFile(ICoreConstants.FRAGMENT_FILENAME_DESCRIPTOR));
189: } else {
190: fModel = new WorkspaceBundlePluginModel(
191: project
192: .getFile(ICoreConstants.BUNDLE_FILENAME_DESCRIPTOR),
193: project
194: .getFile(ICoreConstants.PLUGIN_FILENAME_DESCRIPTOR));
195: }
196: } else {
197: if (fData instanceof IFragmentFieldData) {
198: fModel = new WorkspaceFragmentModel(
199: project
200: .getFile(ICoreConstants.FRAGMENT_FILENAME_DESCRIPTOR),
201: false);
202: } else {
203: fModel = new WorkspacePluginModel(
204: project
205: .getFile(ICoreConstants.PLUGIN_FILENAME_DESCRIPTOR),
206: false);
207: }
208: }
209: IPluginBase pluginBase = fModel.getPluginBase();
210: String targetVersion = ((AbstractFieldData) fData)
211: .getTargetVersion();
212: pluginBase
213: .setSchemaVersion(Double.parseDouble(targetVersion) < 3.2 ? "3.0" : "3.2"); //$NON-NLS-1$ //$NON-NLS-2$
214: pluginBase.setId(fData.getId());
215: pluginBase.setVersion(fData.getVersion());
216: pluginBase.setName(fData.getName());
217: pluginBase.setProviderName(fData.getProvider());
218: if (fModel instanceof IBundlePluginModelBase) {
219: IBundlePluginModelBase bmodel = ((IBundlePluginModelBase) fModel);
220: ((IBundlePluginBase) bmodel.getPluginBase())
221: .setTargetVersion(targetVersion);
222: bmodel.getBundleModel().getBundle().setHeader(
223: Constants.BUNDLE_MANIFESTVERSION, "2"); //$NON-NLS-1$
224: }
225: if (pluginBase instanceof IFragment) {
226: IFragment fragment = (IFragment) pluginBase;
227: IFragmentFieldData data = (IFragmentFieldData) fData;
228: fragment.setPluginId(data.getPluginId());
229: fragment.setPluginVersion(data.getPluginVersion());
230: fragment.setRule(data.getMatch());
231: } else {
232: if (((IPluginFieldData) fData).doGenerateClass())
233: ((IPlugin) pluginBase)
234: .setClassName(((IPluginFieldData) fData)
235: .getClassname());
236: }
237: if (!fData.isSimple()) {
238: setPluginLibraries(fModel);
239: }
240:
241: IPluginReference[] dependencies = getDependencies();
242: for (int i = 0; i < dependencies.length; i++) {
243: IPluginReference ref = dependencies[i];
244: IPluginImport iimport = fModel.getPluginFactory()
245: .createImport();
246: iimport.setId(ref.getId());
247: iimport.setVersion(ref.getVersion());
248: iimport.setMatch(ref.getMatch());
249: pluginBase.add(iimport);
250: }
251: // add Bundle Specific fields if applicable
252: if (pluginBase instanceof BundlePluginBase) {
253: IBundle bundle = ((BundlePluginBase) pluginBase)
254: .getBundle();
255: if (fData instanceof AbstractFieldData) {
256: String framework = ((AbstractFieldData) fData)
257: .getOSGiFramework();
258: if (framework != null) {
259: String value = getCommaValueFromSet(getImportPackagesSet());
260: if (value.length() > 0)
261: bundle.setHeader(Constants.IMPORT_PACKAGE,
262: value);
263: // if framework is not equinox, skip equinox step below to add extra headers
264: if (!framework.equals(ICoreConstants.EQUINOX))
265: return;
266: }
267: }
268: if (fData instanceof IPluginFieldData
269: && ((IPluginFieldData) fData).doGenerateClass()) {
270: if (targetVersion.equals("3.1")) //$NON-NLS-1$
271: bundle.setHeader(ICoreConstants.ECLIPSE_AUTOSTART,
272: "true"); //$NON-NLS-1$
273: else
274: bundle.setHeader(ICoreConstants.ECLIPSE_LAZYSTART,
275: "true"); //$NON-NLS-1$
276: }
277: if (fContentWizard != null) {
278: String[] newFiles = fContentWizard.getNewFiles();
279: if (newFiles != null)
280: for (int i = 0; i < newFiles.length; i++) {
281: if ("plugin.properties".equals(newFiles[i])) { //$NON-NLS-1$
282: bundle.setHeader(
283: Constants.BUNDLE_LOCALIZATION,
284: "plugin"); //$NON-NLS-1$
285: break;
286: }
287: }
288: }
289: }
290: }
291:
292: private IProject createProject() throws CoreException {
293: IProject project = fProjectProvider.getProject();
294: if (!project.exists()) {
295: CoreUtility.createProject(project, fProjectProvider
296: .getLocationPath(), null);
297: project.open(null);
298: }
299: if (!project.hasNature(PDE.PLUGIN_NATURE))
300: CoreUtility.addNatureToProject(project, PDE.PLUGIN_NATURE,
301: null);
302: if (!fData.isSimple() && !project.hasNature(JavaCore.NATURE_ID))
303: CoreUtility.addNatureToProject(project, JavaCore.NATURE_ID,
304: null);
305: if (!fData.isSimple() && fData.getSourceFolderName() != null
306: && fData.getSourceFolderName().trim().length() > 0) {
307: IFolder folder = project.getFolder(fData
308: .getSourceFolderName());
309: if (!folder.exists())
310: CoreUtility.createFolder(folder);
311: }
312: return project;
313: }
314:
315: /*
316: * (non-Javadoc)
317: *
318: * @see org.eclipse.ui.actions.WorkspaceModifyOperation#execute(org.eclipse.core.runtime.IProgressMonitor)
319: */
320: protected void execute(IProgressMonitor monitor)
321: throws CoreException, InvocationTargetException,
322: InterruptedException {
323:
324: // start task
325: monitor.beginTask(
326: PDEUIMessages.NewProjectCreationOperation_creating,
327: getNumberOfWorkUnits());
328: monitor
329: .subTask(PDEUIMessages.NewProjectCreationOperation_project);
330:
331: // create project
332: IProject project = createProject();
333: monitor.worked(1);
334: createContents(monitor, project);
335: // set classpath if project has a Java nature
336: if (project.hasNature(JavaCore.NATURE_ID)) {
337: monitor
338: .subTask(PDEUIMessages.NewProjectCreationOperation_setClasspath);
339: setClasspath(project, fData);
340: monitor.worked(1);
341: }
342:
343: if (fData instanceof PluginFieldData) {
344: PluginFieldData data = (PluginFieldData) fData;
345:
346: // generate top-level Java class if that option is selected
347: if (data.doGenerateClass()) {
348: generateTopLevelPluginClass(project,
349: new SubProgressMonitor(monitor, 1));
350: }
351: }
352: // generate the manifest file
353: monitor
354: .subTask(PDEUIMessages.NewProjectCreationOperation_manifestFile);
355: createManifest(project);
356: monitor.worked(1);
357:
358: // generate the build.properties file
359: monitor
360: .subTask(PDEUIMessages.NewProjectCreationOperation_buildPropertiesFile);
361: createBuildPropertiesFile(project);
362: monitor.worked(1);
363:
364: // generate content contributed by template wizards
365: boolean contentWizardResult = true;
366: if (fContentWizard != null) {
367: contentWizardResult = fContentWizard.performFinish(project,
368: fModel, new SubProgressMonitor(monitor, 1));
369: }
370:
371: if (fData instanceof AbstractFieldData) {
372: String framework = ((AbstractFieldData) fData)
373: .getOSGiFramework();
374: if (framework != null) {
375: IEclipsePreferences pref = new ProjectScope(project)
376: .getNode(PDECore.PLUGIN_ID);
377: if (pref != null) {
378: pref.putBoolean(
379: ICoreConstants.RESOLVE_WITH_REQUIRE_BUNDLE,
380: false);
381: pref.putBoolean(ICoreConstants.EXTENSIONS_PROPERTY,
382: false);
383: if (!ICoreConstants.EQUINOX.equals(framework))
384: pref.putBoolean(
385: ICoreConstants.EQUINOX_PROPERTY, false);
386: try {
387: pref.flush();
388: } catch (BackingStoreException e) {
389: PDEPlugin.logException(e);
390: }
391: }
392: }
393: }
394:
395: if (fData.hasBundleStructure()
396: && fModel instanceof WorkspaceBundlePluginModelBase) {
397: adjustManifests(new SubProgressMonitor(monitor, 1),
398: project, fModel.getPluginBase());
399: }
400:
401: fModel.save();
402: openFile((IFile) fModel.getUnderlyingResource());
403: monitor.worked(1);
404:
405: fResult = contentWizardResult;
406: }
407:
408: private Set getImportPackagesSet() {
409: TreeSet set = new TreeSet();
410: if (fGenerator != null) {
411: String[] packages = fGenerator.getImportPackages();
412: for (int i = 0; i < packages.length; i++) {
413: set.add(packages[i]);
414: }
415: }
416: if (fContentWizard instanceof IBundleContentWizard) {
417: String[] packages = ((IBundleContentWizard) fContentWizard)
418: .getImportPackages();
419: for (int i = 0; i < packages.length; i++) {
420: set.add(packages[i]);
421: }
422: }
423: return set;
424: }
425:
426: protected void fillBinIncludes(IProject project,
427: IBuildEntry binEntry) throws CoreException {
428: if ((!fData.hasBundleStructure() || fContentWizard != null)
429: && ((AbstractFieldData) fData).getOSGiFramework() == null)
430: binEntry
431: .addToken(fData instanceof IFragmentFieldData ? ICoreConstants.FRAGMENT_FILENAME_DESCRIPTOR
432: : ICoreConstants.PLUGIN_FILENAME_DESCRIPTOR);
433: if (fData.hasBundleStructure())
434: binEntry.addToken("META-INF/"); //$NON-NLS-1$
435: if (!fData.isSimple()) {
436: String libraryName = fData.getLibraryName();
437: binEntry.addToken(libraryName == null ? "." : libraryName); //$NON-NLS-1$
438: }
439: if (fContentWizard != null) {
440: String[] files = fContentWizard.getNewFiles();
441: for (int j = 0; j < files.length; j++) {
442: if (!binEntry.contains(files[j]))
443: binEntry.addToken(files[j]);
444: }
445: }
446: }
447:
448: private void generateTopLevelPluginClass(IProject project,
449: IProgressMonitor monitor) throws CoreException {
450: PluginFieldData data = (PluginFieldData) fData;
451: fGenerator = new PluginClassCodeGenerator(project, data
452: .getClassname(), data, fContentWizard != null);
453: fGenerator.generate(monitor);
454: monitor.done();
455: }
456:
457: private IClasspathEntry[] getClassPathEntries(IProject project,
458: IFieldData data) {
459: IClasspathEntry[] internalClassPathEntries = getInternalClassPathEntries(
460: project, data);
461: IClasspathEntry[] entries = new IClasspathEntry[internalClassPathEntries.length + 2];
462: System.arraycopy(internalClassPathEntries, 0, entries, 0,
463: internalClassPathEntries.length);
464: entries[entries.length - 2] = ClasspathComputer
465: .createJREEntry(null);
466: entries[entries.length - 1] = ClasspathComputer
467: .createContainerEntry();
468: return entries;
469: }
470:
471: private IPluginReference[] getDependencies() {
472: ArrayList result = new ArrayList();
473: if (fGenerator != null) {
474: IPluginReference[] refs = fGenerator.getDependencies();
475: for (int i = 0; i < refs.length; i++) {
476: result.add(refs[i]);
477: }
478: }
479:
480: if (fContentWizard != null) {
481: IPluginReference[] refs = fContentWizard
482: .getDependencies(fData.isLegacy() ? null : "3.0"); //$NON-NLS-1$
483: for (int j = 0; j < refs.length; j++) {
484: if (!result.contains(refs[j]))
485: result.add(refs[j]);
486: }
487: }
488: return (IPluginReference[]) result
489: .toArray(new IPluginReference[result.size()]);
490: }
491:
492: protected IClasspathEntry[] getInternalClassPathEntries(
493: IProject project, IFieldData data) {
494: if (data.getSourceFolderName() == null) {
495: return new IClasspathEntry[0];
496: }
497: IClasspathEntry[] entries = new IClasspathEntry[1];
498: IPath path = project.getFullPath().append(
499: data.getSourceFolderName());
500: entries[0] = JavaCore.newSourceEntry(path);
501: return entries;
502: }
503:
504: protected int getNumberOfWorkUnits() {
505: int numUnits = 4;
506: if (fData.hasBundleStructure())
507: numUnits++;
508: if (fData instanceof IPluginFieldData) {
509: IPluginFieldData data = (IPluginFieldData) fData;
510: if (data.doGenerateClass())
511: numUnits++;
512: if (fContentWizard != null)
513: numUnits++;
514: }
515: return numUnits;
516: }
517:
518: public boolean getResult() {
519: return fResult;
520: }
521:
522: private void openFile(final IFile file) {
523: final IWorkbenchWindow ww = PDEPlugin
524: .getActiveWorkbenchWindow();
525: final IWorkbenchPage page = ww.getActivePage();
526: if (page == null)
527: return;
528: final IWorkbenchPart focusPart = page.getActivePart();
529: ww.getShell().getDisplay().asyncExec(new Runnable() {
530: public void run() {
531: if (focusPart instanceof ISetSelectionTarget) {
532: ISelection selection = new StructuredSelection(file);
533: ((ISetSelectionTarget) focusPart)
534: .selectReveal(selection);
535: }
536: try {
537: IDE.openEditor(page, file, true);
538: } catch (PartInitException e) {
539: }
540: }
541: });
542: }
543:
544: private void setClasspath(IProject project, IFieldData data)
545: throws JavaModelException, CoreException {
546: IJavaProject javaProject = JavaCore.create(project);
547: // Set output folder
548: if (data.getOutputFolderName() != null) {
549: IPath path = project.getFullPath().append(
550: data.getOutputFolderName());
551: javaProject.setOutputLocation(path, null);
552: }
553: IClasspathEntry[] entries = getClassPathEntries(project, data);
554: javaProject.setRawClasspath(entries, null);
555: }
556:
557: protected void setPluginLibraries(WorkspacePluginModelBase model)
558: throws CoreException {
559: String libraryName = fData.getLibraryName();
560: if (libraryName == null && !fData.hasBundleStructure()) {
561: libraryName = "."; //$NON-NLS-1$
562: }
563: if (libraryName != null) {
564: IPluginLibrary library = model.getPluginFactory()
565: .createLibrary();
566: library.setName(libraryName);
567: library.setExported(!fData.hasBundleStructure());
568: fModel.getPluginBase().add(library);
569: }
570: }
571:
572: protected String getCommaValueFromSet(Set values) {
573: StringBuffer buffer = new StringBuffer();
574: Iterator iter = values.iterator();
575: while (iter.hasNext()) {
576: if (buffer.length() > 0) {
577: buffer.append(",\n "); //$NON-NLS-1$
578: }
579: buffer.append(iter.next().toString());
580: }
581: return buffer.toString();
582: }
583:
584: private void addAllSourcePackages(IProject project, Set list) {
585: try {
586: IJavaProject javaProject = JavaCore.create(project);
587: IClasspathEntry[] classpath = javaProject.getRawClasspath();
588: for (int i = 0; i < classpath.length; i++) {
589: IClasspathEntry entry = classpath[i];
590: if (entry.getEntryKind() == IClasspathEntry.CPE_SOURCE) {
591: IPath path = entry.getPath().removeFirstSegments(1);
592: if (path.segmentCount() > 0) {
593: IPackageFragmentRoot root = javaProject
594: .getPackageFragmentRoot(project
595: .getFolder(path));
596: IJavaElement[] children = root.getChildren();
597: for (int j = 0; j < children.length; j++) {
598: IPackageFragment frag = (IPackageFragment) children[j];
599: if (frag.getChildren().length > 0
600: || frag.getNonJavaResources().length > 0)
601: list.add(children[j].getElementName());
602: }
603: }
604: }
605: }
606: } catch (JavaModelException e) {
607: }
608: }
609:
610: }
|