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: *******************************************************************************/package org.eclipse.ui.texteditor;
011:
012: import java.net.URL;
013: import com.ibm.icu.text.Collator;
014: import java.util.ArrayList;
015: import java.util.Collections;
016: import java.util.Comparator;
017: import java.util.Iterator;
018: import java.util.List;
019:
020: import org.osgi.framework.Bundle;
021:
022: import org.eclipse.swt.graphics.RGB;
023:
024: import org.eclipse.core.runtime.FileLocator;
025: import org.eclipse.core.runtime.IConfigurationElement;
026: import org.eclipse.core.runtime.IExtensionPoint;
027: import org.eclipse.core.runtime.Path;
028: import org.eclipse.core.runtime.Platform;
029:
030: import org.eclipse.core.resources.IMarker;
031:
032: import org.eclipse.jface.preference.IPreferenceStore;
033: import org.eclipse.jface.preference.PreferenceConverter;
034: import org.eclipse.jface.resource.ImageDescriptor;
035: import org.eclipse.jface.resource.StringConverter;
036:
037: import org.eclipse.ui.editors.text.EditorsUI;
038:
039: import org.eclipse.ui.internal.editors.text.EditorsPlugin;
040:
041: /**
042: * Objects of this class provide access to all extensions declared for the <code>markerAnnotationSpecification</code> extension point.
043: * The extensions are represented as instances of {@link org.eclipse.ui.texteditor.AnnotationPreference}.
044: *
045: * @since 2.1
046: */
047: public class MarkerAnnotationPreferences {
048:
049: /**
050: * Initializes the given preference store with the default marker annotation values.
051: *
052: * @param store the preference store to be initialized
053: * @since 3.0
054: */
055: public static void initializeDefaultValues(IPreferenceStore store) {
056:
057: boolean ignoreAnnotationsPrefPage = store
058: .getBoolean(AbstractDecoratedTextEditorPreferenceConstants.USE_ANNOTATIONS_PREFERENCE_PAGE);
059: boolean ignoreQuickDiffPrefPage = store
060: .getBoolean(AbstractDecoratedTextEditorPreferenceConstants.USE_QUICK_DIFF_PREFERENCE_PAGE);
061:
062: MarkerAnnotationPreferences preferences = EditorsPlugin
063: .getDefault().getMarkerAnnotationPreferences();
064: Iterator e = preferences.getAnnotationPreferences().iterator();
065: while (e.hasNext()) {
066: AnnotationPreference info = (AnnotationPreference) e.next();
067:
068: if (ignoreAnnotationsPrefPage
069: && info.isIncludeOnPreferencePage()
070: && isComplete(info))
071: continue;
072:
073: if (ignoreQuickDiffPrefPage
074: && (info
075: .getAnnotationType()
076: .equals(
077: "org.eclipse.ui.workbench.texteditor.quickdiffChange") //$NON-NLS-1$
078: || (info.getAnnotationType()
079: .equals("org.eclipse.ui.workbench.texteditor.quickdiffAddition")) //$NON-NLS-1$
080: || (info.getAnnotationType()
081: .equals("org.eclipse.ui.workbench.texteditor.quickdiffDeletion")) //$NON-NLS-1$
082: ))
083: continue;
084:
085: store.setDefault(info.getTextPreferenceKey(), info
086: .getTextPreferenceValue());
087: store.setDefault(info.getOverviewRulerPreferenceKey(), info
088: .getOverviewRulerPreferenceValue());
089: if (info.getVerticalRulerPreferenceKey() != null)
090: store.setDefault(info.getVerticalRulerPreferenceKey(),
091: info.getVerticalRulerPreferenceValue());
092: PreferenceConverter.setDefault(store, info
093: .getColorPreferenceKey(), info
094: .getColorPreferenceValue());
095: if (info.getShowInNextPrevDropdownToolbarActionKey() != null)
096: store.setDefault(info
097: .getShowInNextPrevDropdownToolbarActionKey(),
098: info.isShowInNextPrevDropdownToolbarAction());
099: if (info.getIsGoToNextNavigationTargetKey() != null)
100: store.setDefault(info
101: .getIsGoToNextNavigationTargetKey(), info
102: .isGoToNextNavigationTarget());
103: if (info.getIsGoToPreviousNavigationTargetKey() != null)
104: store.setDefault(info
105: .getIsGoToPreviousNavigationTargetKey(), info
106: .isGoToPreviousNavigationTarget());
107: if (info.getHighlightPreferenceKey() != null)
108: store.setDefault(info.getHighlightPreferenceKey(), info
109: .getHighlightPreferenceValue());
110: if (info.getTextStylePreferenceKey() != null)
111: store.setDefault(info.getTextStylePreferenceKey(), info
112: .getTextStyleValue());
113: }
114: }
115:
116: /**
117: * Removes the marker annotation values which are shown on the
118: * general Annotations page from the given store and prevents
119: * setting the default values in the future.
120: * <p>
121: * Note: In order to work this method must be called before any
122: * call to {@link #initializeDefaultValues(IPreferenceStore)}
123: * </p>
124: * <p>
125: * This method is not part of the API and must only be called
126: * by {@link org.eclipse.ui.editors.text.EditorsUI}
127: * </p>
128: *
129: * @param store the preference store to be initialized
130: * @throws IllegalStateException if not called by {@link org.eclipse.ui.editors.text.EditorsUI}
131: * @since 3.0
132: */
133: public static void useAnnotationsPreferencePage(
134: IPreferenceStore store) throws IllegalStateException {
135: checkAccess();
136:
137: store
138: .putValue(
139: AbstractDecoratedTextEditorPreferenceConstants.USE_ANNOTATIONS_PREFERENCE_PAGE,
140: Boolean.toString(true));
141:
142: MarkerAnnotationPreferences preferences = EditorsPlugin
143: .getDefault().getMarkerAnnotationPreferences();
144: Iterator e = preferences.getAnnotationPreferences().iterator();
145: while (e.hasNext()) {
146: AnnotationPreference info = (AnnotationPreference) e.next();
147:
148: // Only reset annotations shown on Annotations preference page
149: if (!info.isIncludeOnPreferencePage() || !isComplete(info))
150: continue;
151:
152: store.setToDefault(info.getTextPreferenceKey());
153: store.setToDefault(info.getOverviewRulerPreferenceKey());
154: if (info.getVerticalRulerPreferenceKey() != null)
155: store
156: .setToDefault(info
157: .getVerticalRulerPreferenceKey());
158: store.setToDefault(info.getColorPreferenceKey());
159: if (info.getShowInNextPrevDropdownToolbarActionKey() != null)
160: store.setToDefault(info
161: .getShowInNextPrevDropdownToolbarActionKey());
162: if (info.getIsGoToNextNavigationTargetKey() != null)
163: store.setToDefault(info
164: .getIsGoToNextNavigationTargetKey());
165: if (info.getIsGoToPreviousNavigationTargetKey() != null)
166: store.setToDefault(info
167: .getIsGoToPreviousNavigationTargetKey());
168: if (info.getHighlightPreferenceKey() != null)
169: store.setToDefault(info.getHighlightPreferenceKey());
170: if (info.getTextStylePreferenceKey() != null)
171: store.setToDefault(info.getTextStylePreferenceKey());
172: }
173: }
174:
175: /**
176: * Removes the Quick Diff marker annotation values which are shown on the
177: * general Quick Diff page from the given store and prevents
178: * setting the default values in the future.
179: * <p>
180: * Note: In order to work this method must be called before any
181: * call to {@link #initializeDefaultValues(IPreferenceStore)}
182: * </p>
183: * <p>
184: * This method is not part of the API and must only be called
185: * by {@link EditorsUI}
186: * </p>
187: *
188: * @param store the preference store to be initialized
189: * @throws IllegalStateException if not called by {@link EditorsUI}
190: * @since 3.0
191: */
192: public static void useQuickDiffPreferencePage(IPreferenceStore store)
193: throws IllegalStateException {
194: checkAccess();
195:
196: store
197: .putValue(
198: AbstractDecoratedTextEditorPreferenceConstants.USE_QUICK_DIFF_PREFERENCE_PAGE,
199: Boolean.toString(true));
200:
201: MarkerAnnotationPreferences preferences = EditorsPlugin
202: .getDefault().getMarkerAnnotationPreferences();
203: Iterator e = preferences.getAnnotationPreferences().iterator();
204: while (e.hasNext()) {
205: AnnotationPreference info = (AnnotationPreference) e.next();
206:
207: // Only reset annotations shown on Quick Diff preference page
208:
209: if (!(info
210: .getAnnotationType()
211: .equals(
212: "org.eclipse.ui.workbench.texteditor.quickdiffChange") //$NON-NLS-1$
213: || (info.getAnnotationType()
214: .equals("org.eclipse.ui.workbench.texteditor.quickdiffAddition")) //$NON-NLS-1$
215: || (info.getAnnotationType()
216: .equals("org.eclipse.ui.workbench.texteditor.quickdiffDeletion")) //$NON-NLS-1$
217: ))
218: continue;
219:
220: store.setToDefault(info.getTextPreferenceKey());
221: store.setToDefault(info.getOverviewRulerPreferenceKey());
222: if (info.getVerticalRulerPreferenceKey() != null)
223: store
224: .setToDefault(info
225: .getVerticalRulerPreferenceKey());
226: store.setToDefault(info.getColorPreferenceKey());
227: if (info.getShowInNextPrevDropdownToolbarActionKey() != null)
228: store.setToDefault(info
229: .getShowInNextPrevDropdownToolbarActionKey());
230: if (info.getIsGoToNextNavigationTargetKey() != null)
231: store.setToDefault(info
232: .getIsGoToNextNavigationTargetKey());
233: if (info.getIsGoToPreviousNavigationTargetKey() != null)
234: store.setToDefault(info
235: .getIsGoToPreviousNavigationTargetKey());
236: if (info.getHighlightPreferenceKey() != null)
237: store.setToDefault(info.getHighlightPreferenceKey());
238: if (info.getTextStylePreferenceKey() != null)
239: store.setToDefault(info.getTextStylePreferenceKey());
240: }
241: }
242:
243: private static final class AccessChecker extends SecurityManager {
244: public Class[] getClassContext() {
245: return super .getClassContext();
246: }
247: }
248:
249: /**
250: * Checks correct access.
251: *
252: * @throws IllegalStateException if not called by {@link EditorsUI}
253: * @since 3.0
254: */
255: private static void checkAccess() throws IllegalStateException {
256: Class[] elements = new AccessChecker().getClassContext();
257: if (!(elements[3].equals(EditorsUI.class) || elements[4]
258: .equals(EditorsUI.class)))
259: throw new IllegalStateException();
260: }
261:
262: /** The list of extension fragments. */
263: private List fFragments;
264: /** The list of extensions. */
265: private List fPreferences;
266:
267: /**
268: * Creates a new marker annotation preferences to access
269: * marker annotation preferences.
270: */
271: public MarkerAnnotationPreferences() {
272: this (false);
273: }
274:
275: /**
276: * Creates a new marker annotation preferences to access
277: * marker annotation preferences.
278: * @param initFromPreferences tells this instance to initialize itself from the preferences
279: *
280: * @since 3.2
281: */
282: private MarkerAnnotationPreferences(boolean initFromPreferences) {
283: if (initFromPreferences)
284: initializeSharedMakerAnnotationPreferences();
285: }
286:
287: /**
288: * Returns all extensions provided for the <code>markerAnnotationSpecification</code> extension point.
289: *
290: * @return all extensions provided for the <code>markerAnnotationSpecification</code> extension point
291: */
292: public List getAnnotationPreferences() {
293: if (fPreferences == null)
294: initialize();
295: return fPreferences;
296: }
297:
298: /**
299: * Returns all extensions provided for the <code>markerAnnotationSpecification</code>
300: * extension point including fragments. Fragments share the preference part
301: * with a marker annotation specifications provided for a super type but do
302: * change the presentation part.
303: *
304: * @return all extensions provided for the <code>markerAnnotationSpecification</code>
305: * extension point including fragments
306: */
307: public List getAnnotationPreferenceFragments() {
308: if (fFragments == null)
309: initialize();
310: return fFragments;
311: }
312:
313: private void initialize() {
314: synchronized (EditorsPlugin.getDefault()) {
315: if (!EditorsPlugin.getDefault()
316: .isMarkerAnnotationPreferencesInitialized())
317: EditorsPlugin.getDefault()
318: .setMarkerAnnotationPreferences(
319: new MarkerAnnotationPreferences(true));
320: }
321:
322: MarkerAnnotationPreferences sharedPrefs = EditorsPlugin
323: .getDefault().getMarkerAnnotationPreferences();
324:
325: fFragments = cloneAnnotationPreferences(sharedPrefs.fFragments);
326: fPreferences = cloneAnnotationPreferences(sharedPrefs.fPreferences);
327: }
328:
329: /**
330: * Reads all extensions provided for the <code>markerAnnotationSpecification</code> extension point and
331: * translates them into <code>AnnotationPreference</code> objects.
332: */
333: private void initializeSharedMakerAnnotationPreferences() {
334:
335: // initialize lists - indicates that the initialization happened
336: fFragments = new ArrayList(2);
337: fPreferences = new ArrayList(2);
338:
339: // populate list
340: IExtensionPoint extensionPoint = Platform
341: .getExtensionRegistry().getExtensionPoint(
342: EditorsUI.PLUGIN_ID,
343: "markerAnnotationSpecification"); //$NON-NLS-1$
344: if (extensionPoint != null) {
345: IConfigurationElement[] elements = extensionPoint
346: .getConfigurationElements();
347: for (int i = 0; i < elements.length; i++) {
348: AnnotationPreference spec = createSpec(elements[i]);
349: if (spec != null)
350: fFragments.add(spec);
351: if (isComplete(spec))
352: fPreferences.add(spec);
353: }
354: }
355:
356: final Collator collator = Collator.getInstance();
357: Collections.sort(fFragments, new Comparator() {
358: /*
359: * @see java.util.Comparator#compare(java.lang.Object, java.lang.Object)
360: */
361: public int compare(Object o1, Object o2) {
362: if (o1 == o2)
363: return 0;
364:
365: AnnotationPreference ap1 = (AnnotationPreference) o1;
366: AnnotationPreference ap2 = (AnnotationPreference) o2;
367:
368: String label1 = ap1.getPreferenceLabel();
369: String label2 = ap2.getPreferenceLabel();
370:
371: if (label1 == null && label2 == null)
372: return 0;
373:
374: if (label1 == null)
375: return -1;
376:
377: if (label2 == null)
378: return 1;
379:
380: return collator.compare(label1, label2);
381: }
382: });
383: }
384:
385: /**
386: * Deeply clones the given list of <code>AnnotationPreference</code>.
387: *
388: * @param annotationPreferences a list of <code>AnnotationPreference</code>
389: * @return the cloned list of cloned annotation preferences
390: * @since 3.1
391: */
392: private List cloneAnnotationPreferences(List annotationPreferences) {
393: if (annotationPreferences == null)
394: return null;
395: List clone = new ArrayList(annotationPreferences.size());
396: Iterator iter = annotationPreferences.iterator();
397: while (iter.hasNext())
398: clone.add(clone(((AnnotationPreference) iter.next())));
399: return clone;
400: }
401:
402: /**
403: * Clones the given annotation preference.
404: *
405: * @param annotationPreference the annotation preference to clone
406: * @return the cloned annotation preference
407: * @since 3.1
408: */
409: private AnnotationPreference clone(
410: AnnotationPreference annotationPreference) {
411: if (annotationPreference == null)
412: return null;
413:
414: AnnotationPreference clone = new AnnotationPreference();
415: if (annotationPreference.getAnnotationType() != null) {
416: clone.setAnnotationType(annotationPreference
417: .getAnnotationType());
418: clone.merge(annotationPreference);
419: }
420:
421: return clone;
422: }
423:
424: /**
425: * Checks if <code>spec</code> has all the attributes previously required
426: * by the marker annotation preference extension point. These are: color, text
427: * and overview ruler preference keys.
428: *
429: * @param spec the <code>AnnotationPreference</code> to check
430: * @return <code>true</code> if <code>spec</code> is complete, <code>false</code> otherwise
431: * @since 3.0
432: */
433: private static boolean isComplete(AnnotationPreference spec) {
434: return spec.getColorPreferenceKey() != null
435: && spec.getColorPreferenceValue() != null
436: && spec.getTextPreferenceKey() != null
437: && spec.getOverviewRulerPreferenceKey() != null;
438: }
439:
440: /**
441: * Creates a <code>AnnotationPreference</code> the given configuration element.
442: *
443: * @param element the configuration element
444: * @return the created annotation preference
445: */
446: private AnnotationPreference createSpec(
447: IConfigurationElement element) {
448:
449: String s;
450: int i;
451: boolean b;
452:
453: ReadOnlyAnnotationPreference info = new ReadOnlyAnnotationPreference();
454:
455: s = element.getAttribute("annotationType"); //$NON-NLS-1$
456: if (s == null || s.trim().length() == 0)
457: return null;
458: info.setAnnotationType(s);
459:
460: s = element.getAttribute("label"); //$NON-NLS-1$
461: if (s != null && s.trim().length() > 0)
462: info.setPreferenceLabel(s);
463:
464: s = element.getAttribute("markerType"); //$NON-NLS-1$
465: if (s != null && s.trim().length() > 0)
466: info.setMarkerType(s);
467:
468: s = element.getAttribute("markerSeverity"); //$NON-NLS-1$
469: if (s != null && s.trim().length() > 0) {
470: i = StringConverter.asInt(s, IMarker.SEVERITY_INFO);
471: info.setSeverity(i);
472: }
473:
474: s = element.getAttribute("textPreferenceKey"); //$NON-NLS-1$
475: if (s != null && s.trim().length() > 0)
476: info.setTextPreferenceKey(s);
477:
478: s = element.getAttribute("textPreferenceValue"); //$NON-NLS-1$
479: if (s != null && s.trim().length() > 0) {
480: b = StringConverter.asBoolean(s, false);
481: info.setTextPreferenceValue(b);
482: }
483:
484: s = element.getAttribute("highlightPreferenceKey"); //$NON-NLS-1$
485: if (s != null && s.trim().length() > 0)
486: info.setHighlightPreferenceKey(s);
487:
488: s = element.getAttribute("highlightPreferenceValue"); //$NON-NLS-1$
489: if (s != null && s.trim().length() > 0) {
490: b = StringConverter.asBoolean(s, false);
491: info.setHighlightPreferenceValue(b);
492: }
493:
494: s = element.getAttribute("overviewRulerPreferenceKey"); //$NON-NLS-1$
495: if (s != null && s.trim().length() > 0)
496: info.setOverviewRulerPreferenceKey(s);
497:
498: s = element.getAttribute("overviewRulerPreferenceValue"); //$NON-NLS-1$
499: if (s != null && s.trim().length() > 0) {
500: b = StringConverter.asBoolean(s, false);
501: info.setOverviewRulerPreferenceValue(b);
502: }
503:
504: s = element.getAttribute("verticalRulerPreferenceKey"); //$NON-NLS-1$
505: if (s != null && s.trim().length() > 0)
506: info.setVerticalRulerPreferenceKey(s);
507:
508: s = element.getAttribute("verticalRulerPreferenceValue"); //$NON-NLS-1$
509: if (s != null && s.trim().length() > 0) {
510: b = StringConverter.asBoolean(s, true);
511: info.setVerticalRulerPreferenceValue(b);
512: }
513:
514: s = element.getAttribute("colorPreferenceKey"); //$NON-NLS-1$
515: if (s != null && s.trim().length() > 0)
516: info.setColorPreferenceKey(s);
517:
518: s = element.getAttribute("colorPreferenceValue"); //$NON-NLS-1$
519: if (s != null && s.trim().length() > 0) {
520: RGB rgb = StringConverter.asRGB(s);
521: info.setColorPreferenceValue(rgb == null ? new RGB(0, 0, 0)
522: : rgb);
523: }
524:
525: s = element.getAttribute("presentationLayer"); //$NON-NLS-1$
526: if (s != null && s.trim().length() > 0) {
527: i = StringConverter.asInt(s, 0);
528: info.setPresentationLayer(i);
529: }
530:
531: s = element.getAttribute("contributesToHeader"); //$NON-NLS-1$
532: if (s != null && s.trim().length() > 0) {
533: b = StringConverter.asBoolean(s, false);
534: info.setContributesToHeader(b);
535: }
536:
537: s = element
538: .getAttribute("showInNextPrevDropdownToolbarActionKey"); //$NON-NLS-1$
539: if (s != null && s.trim().length() > 0)
540: info.setShowInNextPrevDropdownToolbarActionKey(s);
541:
542: s = element.getAttribute("showInNextPrevDropdownToolbarAction"); //$NON-NLS-1$
543: if (s != null && s.trim().length() > 0) {
544: b = StringConverter.asBoolean(s, false);
545: info.setShowInNextPrevDropdownToolbarAction(b);
546: }
547:
548: s = element.getAttribute("isGoToNextNavigationTargetKey"); //$NON-NLS-1$
549: if (s != null && s.trim().length() > 0)
550: info.setIsGoToNextNavigationTargetKey(s);
551:
552: s = element.getAttribute("isGoToNextNavigationTarget"); //$NON-NLS-1$
553: if (s != null && s.trim().length() > 0) {
554: b = StringConverter.asBoolean(s, false);
555: info.setIsGoToNextNavigationTarget(b);
556: }
557:
558: s = element.getAttribute("isGoToPreviousNavigationTargetKey"); //$NON-NLS-1$
559: if (s != null && s.trim().length() > 0)
560: info.setIsGoToPreviousNavigationTargetKey(s);
561:
562: s = element.getAttribute("isGoToPreviousNavigationTarget"); //$NON-NLS-1$
563: if (s != null && s.trim().length() > 0) {
564: b = StringConverter.asBoolean(s, false);
565: info.setIsGoToPreviousNavigationTarget(b);
566: }
567:
568: s = element.getAttribute("symbolicIcon"); //$NON-NLS-1$
569: if (s != null && s.trim().length() > 0)
570: info.setSymbolicImageName(s);
571:
572: s = element.getAttribute("icon"); //$NON-NLS-1$
573: if (s != null && s.trim().length() > 0)
574: info.setImageDescriptor(getImageDescriptor(s, element));
575:
576: s = element.getAttribute("quickFixIcon"); //$NON-NLS-1$
577: if (s != null && s.trim().length() > 0)
578: info.setQuickFixImageDescriptor(getImageDescriptor(s,
579: element));
580:
581: s = element.getAttribute("annotationImageProvider"); //$NON-NLS-1$
582: if (s != null && s.trim().length() > 0)
583: info.setAnnotationImageProviderData(element,
584: "annotationImageProvider"); //$NON-NLS-1$
585:
586: s = element.getAttribute("textStylePreferenceKey"); //$NON-NLS-1$
587: if (s != null && s.trim().length() > 0)
588: info.setTextStylePreferenceKey(s);
589:
590: s = element.getAttribute("textStylePreferenceValue"); //$NON-NLS-1$
591: if (s != null && s.trim().length() > 0) {
592:
593: if (AnnotationPreference.STYLE_BOX.equals(s)
594: || AnnotationPreference.STYLE_DASHED_BOX.equals(s)
595: || AnnotationPreference.STYLE_IBEAM.equals(s)
596: || AnnotationPreference.STYLE_SQUIGGLES.equals(s)
597: || AnnotationPreference.STYLE_UNDERLINE.equals(s))
598: info.setTextStyleValue(s);
599: else
600: info.setTextStyleValue(AnnotationPreference.STYLE_NONE);
601:
602: }
603:
604: s = element.getAttribute("includeOnPreferencePage"); //$NON-NLS-1$
605: info.setIncludeOnPreferencePage(s == null
606: || StringConverter.asBoolean(s, true));
607:
608: info.markReadOnly();
609:
610: return info;
611: }
612:
613: /**
614: * Returns the image descriptor for the icon path specified by the given configuration
615: * element.
616: *
617: * @param iconPath the icon path
618: * @param element the configuration element
619: * @return the image descriptor
620: * @since 3.0
621: */
622: private ImageDescriptor getImageDescriptor(String iconPath,
623: IConfigurationElement element) {
624: String pluginId = element.getContributor().getName();
625: Bundle bundle = Platform.getBundle(pluginId);
626: if (bundle == null)
627: return null;
628:
629: URL url = FileLocator.find(bundle, new Path(iconPath), null);
630: if (url != null)
631: return ImageDescriptor.createFromURL(url);
632:
633: return ImageDescriptor.getMissingImageDescriptor();
634: }
635: }
|