001: /*******************************************************************************
002: * Copyright (c) 2005, 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.internal.menus;
011:
012: import java.util.ArrayList;
013: import java.util.HashMap;
014: import java.util.HashSet;
015: import java.util.Iterator;
016: import java.util.List;
017: import java.util.Map;
018: import java.util.Set;
019:
020: import org.eclipse.core.expressions.Expression;
021: import org.eclipse.core.expressions.IEvaluationContext;
022: import org.eclipse.core.runtime.ISafeRunnable;
023: import org.eclipse.core.runtime.SafeRunner;
024: import org.eclipse.jface.action.ContributionItem;
025: import org.eclipse.jface.action.ContributionManager;
026: import org.eclipse.jface.action.IContributionItem;
027: import org.eclipse.jface.action.IContributionManager;
028: import org.eclipse.jface.action.ICoolBarManager;
029: import org.eclipse.jface.action.IMenuListener;
030: import org.eclipse.jface.action.IMenuManager;
031: import org.eclipse.jface.action.IToolBarManager;
032: import org.eclipse.jface.action.MenuManager;
033: import org.eclipse.jface.action.ToolBarContributionItem;
034: import org.eclipse.jface.action.ToolBarManager;
035: import org.eclipse.jface.internal.provisional.action.IToolBarContributionItem;
036: import org.eclipse.jface.util.IPropertyChangeListener;
037: import org.eclipse.jface.util.PropertyChangeEvent;
038: import org.eclipse.swt.widgets.Control;
039: import org.eclipse.ui.ISourceProvider;
040: import org.eclipse.ui.IWorkbench;
041: import org.eclipse.ui.IWorkbenchWindow;
042: import org.eclipse.ui.PlatformUI;
043: import org.eclipse.ui.activities.ActivityManagerEvent;
044: import org.eclipse.ui.activities.IActivityManagerListener;
045: import org.eclipse.ui.activities.IIdentifier;
046: import org.eclipse.ui.activities.IIdentifierListener;
047: import org.eclipse.ui.activities.IdentifierEvent;
048: import org.eclipse.ui.internal.WorkbenchPlugin;
049: import org.eclipse.ui.internal.WorkbenchWindow;
050: import org.eclipse.ui.internal.expressions.AlwaysEnabledExpression;
051: import org.eclipse.ui.internal.layout.LayoutUtil;
052: import org.eclipse.ui.internal.services.IEvaluationReference;
053: import org.eclipse.ui.internal.services.IEvaluationService;
054: import org.eclipse.ui.internal.util.Util;
055: import org.eclipse.ui.menus.AbstractContributionFactory;
056: import org.eclipse.ui.services.IServiceLocator;
057:
058: /**
059: * <p>
060: * Provides services related to contributing menu elements to the workbench.
061: * </p>
062: * <p>
063: * This class is only intended for internal use within the
064: * <code>org.eclipse.ui.workbench</code> plug-in.
065: * </p>
066: *
067: * @since 3.2
068: */
069: public final class WorkbenchMenuService extends InternalMenuService {
070:
071: /**
072: * A combined property and activity listener that updates the visibility of
073: * contribution items in the new menu system.
074: *
075: * @since 3.3
076: */
077: private final class ContributionItemUpdater implements
078: IPropertyChangeListener, IIdentifierListener {
079:
080: private final IContributionItem item;
081: private IIdentifier identifier;
082: private boolean lastExpressionResult = true;
083:
084: private ContributionItemUpdater(IContributionItem item,
085: IIdentifier identifier) {
086: this .item = item;
087: if (identifier != null) {
088: this .identifier = identifier;
089: this .identifier.addIdentifierListener(this );
090: updateVisibility(); // force initial visibility to fall in line
091: // with activity enablement
092: }
093: }
094:
095: /*
096: * (non-Javadoc)
097: *
098: * @see org.eclipse.jface.util.IPropertyChangeListener#propertyChange(org.eclipse.jface.util.PropertyChangeEvent)
099: */
100: public void propertyChange(PropertyChangeEvent event) {
101: if (event.getProperty() == PROP_VISIBLE) {
102: if (event.getNewValue() != null) {
103: this .lastExpressionResult = ((Boolean) event
104: .getNewValue()).booleanValue();
105: } else {
106: this .lastExpressionResult = false;
107: }
108: updateVisibility();
109: }
110: }
111:
112: private void updateVisibility() {
113: boolean visible = identifier != null ? (identifier
114: .isEnabled() && lastExpressionResult)
115: : lastExpressionResult;
116: item.setVisible(visible);
117:
118: IContributionManager parent = null;
119: if (item instanceof ContributionItem) {
120: parent = ((ContributionItem) item).getParent();
121:
122: } else if (item instanceof MenuManager) {
123: parent = ((MenuManager) item).getParent();
124: }
125: if (parent != null) {
126: parent.markDirty();
127: managersAwaitingUpdates.add(parent);
128: }
129: }
130:
131: /*
132: * (non-Javadoc)
133: *
134: * @see org.eclipse.ui.activities.IIdentifierListener#identifierChanged(org.eclipse.ui.activities.IdentifierEvent)
135: */
136: public void identifierChanged(IdentifierEvent identifierEvent) {
137: updateVisibility();
138: }
139:
140: /**
141: * Dispose of this updater
142: */
143: public void dispose() {
144: identifier.removeIdentifierListener(this );
145: }
146: }
147:
148: /**
149: *
150: */
151: private static final String PROP_VISIBLE = "visible"; //$NON-NLS-1$
152:
153: /**
154: * The class providing persistence for this service.
155: */
156: private final MenuPersistence menuPersistence;
157:
158: /**
159: * The central authority for determining which menus are visible within this
160: * window.
161: */
162: private IEvaluationService evaluationService;
163:
164: private IPropertyChangeListener serviceListener;
165:
166: /**
167: * The service locator into which this service will be inserted.
168: */
169: private IServiceLocator serviceLocator;
170:
171: private IActivityManagerListener activityManagerListener;
172:
173: /**
174: * Constructs a new instance of <code>MenuService</code> using a menu
175: * manager.
176: */
177: public WorkbenchMenuService(IServiceLocator serviceLocator) {
178: this .menuPersistence = new MenuPersistence(this );
179: this .serviceLocator = serviceLocator;
180: evaluationService = (IEvaluationService) serviceLocator
181: .getService(IEvaluationService.class);
182: evaluationService.addServiceListener(getServiceListener());
183: ((IWorkbench) serviceLocator.getService(IWorkbench.class))
184: .getActivitySupport().getActivityManager()
185: .addActivityManagerListener(
186: getActivityManagerListener());
187: }
188:
189: /**
190: * @return
191: */
192: private IActivityManagerListener getActivityManagerListener() {
193: if (activityManagerListener == null) {
194: activityManagerListener = new IActivityManagerListener() {
195:
196: public void activityManagerChanged(
197: ActivityManagerEvent activityManagerEvent) {
198: if (activityManagerEvent
199: .haveEnabledActivityIdsChanged()) {
200: updateManagers(); // called after all identifiers have
201: // been update - now update the
202: // managers
203: }
204:
205: }
206: };
207: }
208: return activityManagerListener;
209: }
210:
211: /**
212: * @return
213: */
214: private IPropertyChangeListener getServiceListener() {
215: if (serviceListener == null) {
216: serviceListener = new IPropertyChangeListener() {
217: public void propertyChange(PropertyChangeEvent event) {
218: if (event.getProperty().equals(
219: IEvaluationService.PROP_NOTIFYING)) {
220: if (!((Boolean) event.getNewValue())
221: .booleanValue()) {
222: // if it's false, the evaluation service has
223: // finished
224: // with its latest round of updates
225: updateManagers();
226: }
227: }
228: }
229: };
230: }
231: return serviceListener;
232: }
233:
234: private void updateManagers() {
235: Object[] managers = managersAwaitingUpdates.toArray();
236: managersAwaitingUpdates.clear();
237: for (int i = 0; i < managers.length; i++) {
238: IContributionManager mgr = (IContributionManager) managers[i];
239: mgr.update(true);
240: if (mgr instanceof ToolBarManager) {
241: if (!updateCoolBar((ToolBarManager) mgr)) {
242: updateTrim((ToolBarManager) mgr);
243: }
244: } else if (mgr instanceof MenuManager) {
245: IContributionManager parent = ((MenuManager) mgr)
246: .getParent();
247: if (parent != null) {
248: parent.update(true);
249: }
250: }
251: }
252: }
253:
254: private void updateTrim(ToolBarManager mgr) {
255: Control control = mgr.getControl();
256: if (control == null || control.isDisposed()) {
257: return;
258: }
259: LayoutUtil.resize(control);
260: }
261:
262: private boolean updateCoolBar(ToolBarManager mgr) {
263: IWorkbenchWindow[] windows = PlatformUI.getWorkbench()
264: .getWorkbenchWindows();
265: for (int i = 0; i < windows.length; i++) {
266: WorkbenchWindow window = (WorkbenchWindow) windows[i];
267: ICoolBarManager cb = window.getCoolBarManager2();
268: if (cb != null) {
269: IContributionItem[] items = cb.getItems();
270: for (int j = 0; j < items.length; j++) {
271: if (items[j] instanceof ToolBarContributionItem) {
272: IToolBarManager tbm = ((ToolBarContributionItem) items[j])
273: .getToolBarManager();
274: if (mgr == tbm) {
275: cb.update(true);
276: return true;
277: }
278: }
279: }
280: }
281: }
282: return false;
283: }
284:
285: public final void addSourceProvider(final ISourceProvider provider) {
286: // no-op
287: }
288:
289: public final void dispose() {
290: menuPersistence.dispose();
291: Iterator i = evaluationsByItem.values().iterator();
292: while (i.hasNext()) {
293: IEvaluationReference ref = (IEvaluationReference) i.next();
294: evaluationService.removeEvaluationListener(ref);
295: }
296: evaluationsByItem.clear();
297: managersAwaitingUpdates.clear();
298: if (serviceListener != null) {
299: evaluationService.removeServiceListener(serviceListener);
300: serviceListener = null;
301: }
302: }
303:
304: public final void readRegistry() {
305: menuPersistence.read();
306: }
307:
308: public final void removeSourceProvider(
309: final ISourceProvider provider) {
310: // no-op
311: }
312:
313: //
314: // 3.3 common menu service information
315: //
316: private Map uriToManager = new HashMap();
317:
318: private Map contributionManagerTracker = new HashMap();
319:
320: private IMenuListener menuTrackerListener;
321:
322: private Map evaluationsByItem = new HashMap();
323:
324: private Map activityListenersByItem = new HashMap();
325:
326: private Set managersAwaitingUpdates = new HashSet();
327:
328: /**
329: * Construct an 'id' string from the given URI. The resulting 'id' is the
330: * part of the URI not containing the query:
331: * <p>
332: * i.e. [menu | popup | toolbar]:id
333: * </p>
334: *
335: * @param uri
336: * The URI to construct the id from
337: * @return The id
338: */
339: private String getIdFromURI(MenuLocationURI uri) {
340: return uri.getScheme() + ":" + uri.getPath(); //$NON-NLS-1$;
341: }
342:
343: public List getAdditionsForURI(MenuLocationURI uri) {
344: if (uri == null)
345: return null;
346:
347: List caches = (List) uriToManager.get(getIdFromURI(uri));
348:
349: // we always return a list
350: if (caches == null) {
351: caches = new ArrayList();
352: uriToManager.put(getIdFromURI(uri), caches);
353: }
354:
355: return caches;
356: }
357:
358: /*
359: * (non-Javadoc)
360: *
361: * @see org.eclipse.ui.internal.menus.IMenuService#addCacheForURI(org.eclipse.ui.internal.menus.MenuCacheEntry)
362: */
363: public void addContributionFactory(
364: AbstractContributionFactory factory) {
365: if (factory == null || factory.getLocation() == null)
366: return;
367:
368: MenuLocationURI uri = new MenuLocationURI(factory.getLocation());
369: String cacheId = getIdFromURI(uri);
370: List caches = (List) uriToManager.get(cacheId);
371:
372: // we always return a list
373: if (caches == null) {
374: caches = new ArrayList();
375: uriToManager.put(cacheId, caches);
376: }
377: caches.add(factory);
378: }
379:
380: /*
381: * (non-Javadoc)
382: *
383: * @see org.eclipse.ui.menus.IMenuService#removeContributionFactory(org.eclipse.ui.menus.AbstractContributionFactory)
384: */
385: public void removeContributionFactory(
386: AbstractContributionFactory factory) {
387: if (factory == null || factory.getLocation() == null)
388: return;
389:
390: MenuLocationURI uri = new MenuLocationURI(factory.getLocation());
391: String cacheId = getIdFromURI(uri);
392: List caches = (List) uriToManager.get(cacheId);
393: if (caches != null) {
394: caches.remove(factory);
395: }
396: }
397:
398: private boolean processAdditions(
399: final IServiceLocator serviceLocatorToUse,
400: Expression restriction, final ContributionManager mgr,
401: final AbstractContributionFactory cache,
402: final Set itemsAdded) {
403: final int idx = getInsertionIndex(mgr, cache.getLocation());
404: if (idx == -1)
405: return false; // can't process (yet)
406:
407: // Get the additions
408: final ContributionRoot ciList = new ContributionRoot(this ,
409: restriction, cache.getNamespace());
410:
411: ISafeRunnable run = new ISafeRunnable() {
412:
413: public void handleException(Throwable exception) {
414: // TODO Auto-generated method stub
415:
416: }
417:
418: public void run() throws Exception {
419: int insertionIndex = idx;
420: cache.createContributionItems(serviceLocatorToUse,
421: ciList);
422:
423: // If we have any then add them at the correct location
424: if (ciList.getItems().size() > 0) {
425: track(mgr, cache, ciList);
426: for (Iterator ciIter = ciList.getItems().iterator(); ciIter
427: .hasNext();) {
428: IContributionItem ici = (IContributionItem) ciIter
429: .next();
430: if (ici.getId() != null) {
431: itemsAdded.add(ici.getId());
432: }
433: final int oldSize = mgr.getSize();
434: mgr.insert(insertionIndex, ici);
435: if (mgr.getSize() > oldSize)
436: insertionIndex++;
437: }
438: }
439: }
440: };
441: SafeRunner.run(run);
442:
443: return true;
444: }
445:
446: /**
447: * @param mgr
448: * @param cache
449: * @param ciList
450: */
451: private void track(ContributionManager mgr,
452: AbstractContributionFactory cache, ContributionRoot ciList) {
453: List contributions = (List) contributionManagerTracker.get(mgr);
454: if (contributions == null) {
455: contributions = new ArrayList();
456: contributionManagerTracker.put(mgr, contributions);
457: if (mgr instanceof IMenuManager) {
458: IMenuManager m = (IMenuManager) mgr;
459: if (m.getRemoveAllWhenShown()) {
460: m.addMenuListener(getMenuTrackerListener());
461: }
462: }
463: }
464: contributions.add(ciList);
465: }
466:
467: /**
468: * @return
469: */
470: private IMenuListener getMenuTrackerListener() {
471: if (menuTrackerListener == null) {
472: menuTrackerListener = new IMenuListener() {
473: public void menuAboutToShow(IMenuManager manager) {
474: sweepContributions(manager);
475: }
476: };
477: }
478: return menuTrackerListener;
479: }
480:
481: /**
482: * @param manager
483: */
484: protected void sweepContributions(IMenuManager manager) {
485: List contributions = (List) contributionManagerTracker
486: .get(manager);
487: if (contributions == null) {
488: return;
489: }
490: Iterator i = contributions.iterator();
491: while (i.hasNext()) {
492: final ContributionRoot items = (ContributionRoot) i.next();
493: boolean removed = false;
494: Iterator j = items.getItems().iterator();
495: while (j.hasNext()) {
496: IContributionItem item = (IContributionItem) j.next();
497: if (item instanceof ContributionItem
498: && ((ContributionItem) item).getParent() == null) {
499: removed = true;
500: releaseItem(item);
501: }
502: }
503: if (removed) {
504: releaseCache(items);
505: i.remove();
506: }
507: }
508: }
509:
510: /**
511: * @param items
512: */
513: private void releaseCache(final ContributionRoot items) {
514: items.release();
515: }
516:
517: /*
518: * (non-Javadoc)
519: *
520: * @see org.eclipse.ui.internal.menus.IMenuService#populateMenu(org.eclipse.jface.action.ContributionManager,
521: * org.eclipse.ui.internal.menus.MenuLocationURI)
522: */
523: public void populateContributionManager(ContributionManager mgr,
524: String uri) {
525: populateContributionManager(serviceLocator, null, mgr, uri,
526: true);
527: }
528:
529: public void populateContributionManager(
530: IServiceLocator serviceLocatorToUse,
531: Expression restriction, ContributionManager mgr,
532: String uri, boolean recurse) {
533: MenuLocationURI contributionLocation = new MenuLocationURI(uri);
534: List additionCaches = getAdditionsForURI(contributionLocation);
535:
536: List retryList = new ArrayList();
537: Set itemsAdded = new HashSet();
538: for (Iterator iterator = additionCaches.iterator(); iterator
539: .hasNext();) {
540: AbstractContributionFactory cache = (AbstractContributionFactory) iterator
541: .next();
542: if (!processAdditions(serviceLocatorToUse, restriction,
543: mgr, cache, itemsAdded)) {
544: retryList.add(cache);
545: }
546: }
547:
548: // OK, iteratively loop through entries whose URI's could not
549: // be resolved until we either run out of entries or the list
550: // doesn't change size (indicating that the remaining entries
551: // can never be resolved).
552: boolean done = retryList.size() == 0;
553: while (!done) {
554: // Clone the retry list and clear it
555: List curRetry = new ArrayList(retryList);
556: int retryCount = retryList.size();
557: retryList.clear();
558:
559: // Walk the current list seeing if any entries can now be resolved
560: for (Iterator iterator = curRetry.iterator(); iterator
561: .hasNext();) {
562: AbstractContributionFactory cache = (AbstractContributionFactory) iterator
563: .next();
564: if (!processAdditions(serviceLocatorToUse, restriction,
565: mgr, cache, itemsAdded))
566: retryList.add(cache);
567: }
568:
569: // We're done if the retryList is now empty (everything done) or
570: // if the list hasn't changed at all (no hope)
571: done = (retryList.size() == 0)
572: || (retryList.size() == retryCount);
573: }
574:
575: // Now, recurse through any sub-menus
576: IContributionItem[] curItems = mgr.getItems();
577: for (int i = 0; i < curItems.length; i++) {
578: if (curItems[i] instanceof ContributionManager) {
579: String id = curItems[i].getId();
580: if (id != null && id.length() > 0
581: && (recurse || itemsAdded.contains(id))) {
582: populateContributionManager(
583: serviceLocatorToUse,
584: restriction,
585: (ContributionManager) curItems[i],
586: contributionLocation.getScheme() + ":" + id, true); //$NON-NLS-1$
587: }
588: } else if (curItems[i] instanceof IToolBarContributionItem) {
589: IToolBarContributionItem tbci = (IToolBarContributionItem) curItems[i];
590: if (tbci.getId() != null
591: && tbci.getId().length() > 0
592: && (recurse || itemsAdded
593: .contains(tbci.getId()))) {
594: populateContributionManager(serviceLocatorToUse,
595: restriction, (ContributionManager) tbci
596: .getToolBarManager(),
597: contributionLocation.getScheme()
598: + ":" + tbci.getId(), true); //$NON-NLS-1$
599: }
600: }
601: }
602: }
603:
604: /**
605: * @param mgr
606: * @param uri
607: * @return
608: */
609: private int getInsertionIndex(ContributionManager mgr,
610: String location) {
611: MenuLocationURI uri = new MenuLocationURI(location);
612: String query = uri.getQuery();
613:
614: int additionsIndex = -1;
615:
616: // No Query means 'after=additions' (if ther) or
617: // the end of the menu
618: if (query.length() == 0 || query.equals("after=additions")) { //$NON-NLS-1$
619: additionsIndex = mgr.indexOf("additions"); //$NON-NLS-1$
620: if (additionsIndex == -1)
621: additionsIndex = mgr.getItems().length;
622: else
623: ++additionsIndex;
624: } else {
625: // Should be in the form "[before|after]=id"
626: String[] queryParts = Util.split(query, '=');
627: if (queryParts[1].length() > 0) {
628: additionsIndex = mgr.indexOf(queryParts[1]);
629: if (additionsIndex != -1
630: && queryParts[0].equals("after")) //$NON-NLS-1$
631: additionsIndex++;
632: }
633: }
634:
635: return additionsIndex;
636: }
637:
638: /*
639: * (non-Javadoc)
640: *
641: * @see org.eclipse.ui.internal.menus.IMenuService#getCurrentState()
642: */
643: public IEvaluationContext getCurrentState() {
644: return evaluationService.getCurrentState();
645: }
646:
647: /*
648: * (non-Javadoc)
649: *
650: * @see org.eclipse.ui.internal.menus.IMenuService#registerVisibleWhen(org.eclipse.jface.action.IContributionItem,
651: * org.eclipse.core.expressions.Expression)
652: */
653: public void registerVisibleWhen(final IContributionItem item,
654: final Expression visibleWhen, final Expression restriction,
655: String identifierID) {
656: if (item == null) {
657: throw new IllegalArgumentException("item cannot be null"); //$NON-NLS-1$
658: }
659: if (visibleWhen == null) {
660: throw new IllegalArgumentException(
661: "visibleWhen expression cannot be null"); //$NON-NLS-1$
662: }
663: if (evaluationsByItem.get(item) != null) {
664: final String id = item.getId();
665: WorkbenchPlugin.log("item is already registered: " //$NON-NLS-1$
666: + (id == null ? "no id" : id)); //$NON-NLS-1$
667: return;
668: }
669: IIdentifier identifier = null;
670: if (identifierID != null) {
671: identifier = PlatformUI.getWorkbench().getActivitySupport()
672: .getActivityManager().getIdentifier(identifierID);
673: }
674: ContributionItemUpdater listener = new ContributionItemUpdater(
675: item, identifier);
676:
677: if (visibleWhen != AlwaysEnabledExpression.INSTANCE) {
678: IEvaluationReference ref = evaluationService
679: .addEvaluationListener(visibleWhen, listener,
680: PROP_VISIBLE, restriction);
681: evaluationsByItem.put(item, ref);
682: }
683: activityListenersByItem.put(item, listener);
684: }
685:
686: /*
687: * (non-Javadoc)
688: *
689: * @see org.eclipse.ui.internal.menus.IMenuService#unregisterVisibleWhen(org.eclipse.jface.action.IContributionItem)
690: */
691: public void unregisterVisibleWhen(IContributionItem item) {
692: ContributionItemUpdater identifierListener = (ContributionItemUpdater) activityListenersByItem
693: .get(item);
694: if (identifierListener != null) {
695: identifierListener.dispose();
696: }
697:
698: IEvaluationReference ref = (IEvaluationReference) evaluationsByItem
699: .remove(item);
700: if (ref == null) {
701: return;
702: }
703:
704: evaluationService.removeEvaluationListener(ref);
705: }
706:
707: /*
708: * (non-Javadoc)
709: *
710: * @see org.eclipse.ui.internal.menus.IMenuService#releaseMenu(org.eclipse.jface.action.ContributionManager)
711: */
712: public void releaseContributions(ContributionManager mgr) {
713: List contributions = (List) contributionManagerTracker
714: .remove(mgr);
715: if (contributions == null) {
716: return;
717: }
718:
719: if (mgr instanceof IMenuManager) {
720: IMenuManager m = (IMenuManager) mgr;
721: if (m.getRemoveAllWhenShown()) {
722: m.removeMenuListener(getMenuTrackerListener());
723: }
724: }
725:
726: Iterator i = contributions.iterator();
727: while (i.hasNext()) {
728: final ContributionRoot items = (ContributionRoot) i.next();
729: Iterator j = items.getItems().iterator();
730: while (j.hasNext()) {
731: IContributionItem item = (IContributionItem) j.next();
732: releaseItem(item);
733: }
734: releaseCache(items);
735: }
736: contributions.clear();
737: }
738:
739: /**
740: * @param item
741: */
742: private void releaseItem(IContributionItem item) {
743: unregisterVisibleWhen(item);
744: if (item instanceof ContributionManager) {
745: releaseContributions((ContributionManager) item);
746: } else if (item instanceof IToolBarContributionItem) {
747: IToolBarContributionItem tbci = (IToolBarContributionItem) item;
748: releaseContributions((ContributionManager) tbci
749: .getToolBarManager());
750: }
751: }
752: }
|