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.pde.internal.ui.search.dependencies;
011:
012: import java.io.FileNotFoundException;
013: import java.io.FileOutputStream;
014: import java.io.PrintWriter;
015: import java.lang.reflect.InvocationTargetException;
016: import java.util.ArrayList;
017: import java.util.Collection;
018: import java.util.HashMap;
019: import java.util.HashSet;
020: import java.util.Iterator;
021: import java.util.LinkedList;
022: import java.util.List;
023: import java.util.Map;
024: import java.util.Set;
025: import java.util.Stack;
026: import java.util.StringTokenizer;
027:
028: import org.eclipse.core.resources.IFile;
029: import org.eclipse.core.resources.IProject;
030: import org.eclipse.core.resources.IResource;
031: import org.eclipse.core.resources.ProjectScope;
032: import org.eclipse.core.runtime.CoreException;
033: import org.eclipse.core.runtime.IProgressMonitor;
034: import org.eclipse.core.runtime.SubProgressMonitor;
035: import org.eclipse.jdt.core.IJavaElement;
036: import org.eclipse.jdt.core.IJavaProject;
037: import org.eclipse.jdt.core.IPackageFragmentRoot;
038: import org.eclipse.jdt.core.IParent;
039: import org.eclipse.jdt.core.JavaCore;
040: import org.eclipse.jdt.core.JavaModelException;
041: import org.eclipse.jdt.core.search.IJavaSearchConstants;
042: import org.eclipse.jdt.core.search.IJavaSearchScope;
043: import org.eclipse.jdt.core.search.SearchEngine;
044: import org.eclipse.jdt.core.search.SearchMatch;
045: import org.eclipse.jdt.core.search.SearchParticipant;
046: import org.eclipse.jdt.core.search.SearchPattern;
047: import org.eclipse.jdt.core.search.SearchRequestor;
048: import org.eclipse.osgi.service.resolver.BaseDescription;
049: import org.eclipse.osgi.service.resolver.BundleDescription;
050: import org.eclipse.osgi.service.resolver.BundleSpecification;
051: import org.eclipse.osgi.service.resolver.ExportPackageDescription;
052: import org.eclipse.osgi.service.resolver.ImportPackageSpecification;
053: import org.eclipse.osgi.util.ManifestElement;
054: import org.eclipse.osgi.util.NLS;
055: import org.eclipse.pde.core.build.IBuild;
056: import org.eclipse.pde.core.build.IBuildEntry;
057: import org.eclipse.pde.core.plugin.IPluginBase;
058: import org.eclipse.pde.core.plugin.IPluginImport;
059: import org.eclipse.pde.core.plugin.IPluginModelBase;
060: import org.eclipse.pde.core.plugin.PluginRegistry;
061: import org.eclipse.pde.internal.core.ICoreConstants;
062: import org.eclipse.pde.internal.core.PDECore;
063: import org.eclipse.pde.internal.core.build.WorkspaceBuildModel;
064: import org.eclipse.pde.internal.core.bundle.BundlePluginBase;
065: import org.eclipse.pde.internal.core.converter.PluginConverter;
066: import org.eclipse.pde.internal.core.ibundle.IBundle;
067: import org.eclipse.pde.internal.core.ibundle.IBundlePluginModelBase;
068: import org.eclipse.pde.internal.core.ibundle.IManifestHeader;
069: import org.eclipse.pde.internal.core.plugin.PluginImport;
070: import org.eclipse.pde.internal.core.search.PluginJavaSearchUtil;
071: import org.eclipse.pde.internal.core.text.bundle.ImportPackageHeader;
072: import org.eclipse.pde.internal.core.text.bundle.ImportPackageObject;
073: import org.eclipse.pde.internal.core.text.bundle.RequireBundleHeader;
074: import org.eclipse.pde.internal.core.text.bundle.RequireBundleObject;
075: import org.eclipse.pde.internal.ui.PDEUIMessages;
076: import org.eclipse.ui.actions.WorkspaceModifyOperation;
077: import org.osgi.framework.BundleException;
078: import org.osgi.framework.Constants;
079: import org.osgi.framework.Version;
080:
081: public class AddNewDependenciesOperation extends
082: WorkspaceModifyOperation {
083:
084: protected IProject fProject;
085: protected IBundlePluginModelBase fBase;
086: private boolean fNewDependencies = false;
087:
088: protected static class ReferenceFinder extends SearchRequestor {
089: private boolean found = false;
090:
091: public void acceptSearchMatch(SearchMatch match)
092: throws CoreException {
093: found = true;
094: }
095:
096: public boolean foundMatches() {
097: return found;
098: }
099: }
100:
101: public AddNewDependenciesOperation(IProject project,
102: IBundlePluginModelBase base) {
103: fProject = project;
104: fBase = base;
105: }
106:
107: protected void execute(IProgressMonitor monitor)
108: throws CoreException, InvocationTargetException,
109: InterruptedException {
110: monitor
111: .beginTask(
112: PDEUIMessages.AddNewDependenciesOperation_mainTask,
113: 100);
114: final IBundle bundle = fBase.getBundleModel().getBundle();
115: final Set ignorePkgs = new HashSet();
116: final String[] secDeps = findSecondaryBundles(bundle,
117: ignorePkgs);
118: if (secDeps == null || secDeps.length == 0) {
119: monitor.done();
120: return;
121: }
122: monitor.worked(4);
123: findImportPackages(bundle, ignorePkgs);
124: monitor.worked(2);
125: addProjectPackages(bundle, ignorePkgs);
126: monitor.worked(4);
127:
128: final Map additionalDeps = new HashMap();
129: monitor
130: .subTask(PDEUIMessages.AddNewDependenciesOperation_searchProject);
131:
132: boolean useRequireBundle = new ProjectScope(fProject).getNode(
133: PDECore.PLUGIN_ID).getBoolean(
134: ICoreConstants.RESOLVE_WITH_REQUIRE_BUNDLE, true);
135: findSecondaryDependencies(secDeps, ignorePkgs, additionalDeps,
136: bundle, useRequireBundle, new SubProgressMonitor(
137: monitor, 80));
138: handleNewDependencies(additionalDeps, useRequireBundle,
139: new SubProgressMonitor(monitor, 10));
140: monitor.done();
141: }
142:
143: public boolean foundNewDependencies() {
144: return fNewDependencies;
145: }
146:
147: protected String[] findSecondaryBundles(IBundle bundle,
148: Set ignorePkgs) {
149: String[] secDeps = getSecondaryDependencies();
150: if (secDeps == null)
151: return null;
152: Set manifestPlugins = findManifestPlugins(bundle, ignorePkgs);
153:
154: List result = new LinkedList();
155: for (int i = 0; i < secDeps.length; i++)
156: if (!manifestPlugins.contains(secDeps[i]))
157: result.add(secDeps[i]);
158:
159: return (String[]) result.toArray(new String[result.size()]);
160: }
161:
162: private String[] getSecondaryDependencies() {
163: IBuild build = getBuild();
164: if (build != null) {
165: IBuildEntry be = build
166: .getEntry(IBuildEntry.SECONDARY_DEPENDENCIES);
167: if (be != null)
168: return be.getTokens();
169: }
170: return null;
171: }
172:
173: protected final IBuild getBuild() {
174: IFile buildProps = fProject.getFile("build.properties"); //$NON-NLS-1$
175: if (buildProps != null) {
176: WorkspaceBuildModel model = new WorkspaceBuildModel(
177: buildProps);
178: if (model != null)
179: return model.getBuild();
180: }
181: return null;
182: }
183:
184: private Set findManifestPlugins(IBundle bundle, Set ignorePkgs) {
185: IManifestHeader header = bundle
186: .getManifestHeader(Constants.REQUIRE_BUNDLE);
187: if (header == null)
188: return new HashSet(0);
189: Set plugins = (header instanceof RequireBundleHeader) ? findManifestPlugins(
190: (RequireBundleHeader) header, ignorePkgs)
191: : findManifestPlugins(ignorePkgs);
192: if (plugins.contains("org.eclipse.core.runtime")) //$NON-NLS-1$
193: plugins.add("system.bundle"); //$NON-NLS-1$
194: return plugins;
195: }
196:
197: private Set findManifestPlugins(RequireBundleHeader header,
198: Set ignorePkgs) {
199: RequireBundleObject[] bundles = header.getRequiredBundles();
200: Set result = new HashSet((4 / 3) * (bundles.length) + 2);
201: ArrayList plugins = new ArrayList();
202: for (int i = 0; i < bundles.length; i++) {
203: String id = bundles[i].getId();
204: result.add(id);
205: IPluginModelBase base = PluginRegistry.findModel(id);
206: if (base != null) {
207: ExportPackageDescription[] exportedPkgs = findExportedPackages(base
208: .getBundleDescription());
209: for (int j = 0; j < exportedPkgs.length; j++)
210: ignorePkgs.add(exportedPkgs[j].getName());
211: plugins.add(base.getPluginBase());
212: }
213: }
214: return result;
215: }
216:
217: private Set findManifestPlugins(Set ignorePkgs) {
218: BundleSpecification[] bundles = fBase.getBundleDescription()
219: .getRequiredBundles();
220: Set result = new HashSet((4 / 3) * (bundles.length) + 2);
221: ArrayList plugins = new ArrayList();
222: for (int i = 0; i < bundles.length; i++) {
223: String id = bundles[i].getName();
224: result.add(id);
225: IPluginModelBase base = PluginRegistry.findModel(id);
226: if (base != null) {
227: ExportPackageDescription[] exportedPkgs = findExportedPackages(base
228: .getBundleDescription());
229: for (int j = 0; j < exportedPkgs.length; j++)
230: ignorePkgs.add(exportedPkgs[j].getName());
231: plugins.add(base.getPluginBase());
232: }
233: }
234: return result;
235: }
236:
237: protected final ExportPackageDescription[] findExportedPackages(
238: BundleDescription desc) {
239: if (desc != null) {
240: IBundle bundle = fBase.getBundleModel().getBundle();
241: String value = bundle
242: .getHeader(Constants.BUNDLE_SYMBOLICNAME);
243: int index = (value != null) ? value.indexOf(';') : -1;
244: String projectBundleId = (index > 0) ? value.substring(0,
245: index) : value;
246: List result = new LinkedList();
247: Stack stack = new Stack();
248: stack.add(desc);
249: while (!stack.isEmpty()) {
250: BundleDescription bdesc = (BundleDescription) stack
251: .pop();
252: ExportPackageDescription[] expkgs = bdesc
253: .getExportPackages();
254: for (int i = 0; i < expkgs.length; i++)
255: if (addPackage(projectBundleId, expkgs[i]))
256: result.add(expkgs[i]);
257:
258: // Look at re-exported Require-Bundles for any other exported packages
259: BundleSpecification[] requiredBundles = bdesc
260: .getRequiredBundles();
261: for (int i = 0; i < requiredBundles.length; i++)
262: if (requiredBundles[i].isExported()) {
263: BaseDescription bd = requiredBundles[i]
264: .getSupplier();
265: if (bd != null
266: && bd instanceof BundleDescription)
267: stack.add(bd);
268: }
269: }
270: return (ExportPackageDescription[]) result
271: .toArray(new ExportPackageDescription[result.size()]);
272: }
273: return new ExportPackageDescription[0];
274: }
275:
276: private boolean addPackage(String symbolicName,
277: ExportPackageDescription pkg) {
278: if (symbolicName == null)
279: return true;
280: String[] friends = (String[]) pkg
281: .getDirective(ICoreConstants.FRIENDS_DIRECTIVE);
282: if (friends != null) {
283: for (int i = 0; i < friends.length; i++) {
284: if (symbolicName.equals(friends[i]))
285: return true;
286: }
287: return false;
288: }
289: return !(((Boolean) pkg
290: .getDirective(ICoreConstants.INTERNAL_DIRECTIVE))
291: .booleanValue());
292: }
293:
294: protected final void findImportPackages(IBundle bundle,
295: Set ignorePkgs) {
296: IManifestHeader header = bundle
297: .getManifestHeader(Constants.IMPORT_PACKAGE);
298: if (header == null || header.getValue() == null)
299: return;
300: if (header instanceof ImportPackageHeader) {
301: ImportPackageObject[] pkgs = ((ImportPackageHeader) header)
302: .getPackages();
303: for (int i = 0; i < pkgs.length; i++)
304: ignorePkgs.add(pkgs[i].getName());
305: } else {
306: ImportPackageSpecification[] pkgs = fBase
307: .getBundleDescription().getImportPackages();
308: for (int i = 0; i < pkgs.length; i++)
309: ignorePkgs.add(pkgs[i].getName());
310: }
311: }
312:
313: protected void findSecondaryDependencies(String[] secDeps,
314: Set ignorePkgs, Map newDeps, IBundle bundle,
315: boolean useRequireBundle, IProgressMonitor monitor) {
316: IJavaProject jProject = JavaCore.create(fProject);
317: SearchEngine engine = new SearchEngine();
318: if (ignorePkgs == null)
319: ignorePkgs = new HashSet(2);
320: monitor
321: .beginTask(
322: PDEUIMessages.AddNewDependenciesOperation_searchProject,
323: secDeps.length);
324: for (int j = 0; j < secDeps.length; j++) {
325: try {
326: if (monitor.isCanceled())
327: return;
328: IProgressMonitor subMonitor = new SubProgressMonitor(
329: monitor, 1);
330: String pluginId = secDeps[j];
331: IPluginModelBase base = PluginRegistry
332: .findModel(secDeps[j]);
333: if (base != null) {
334: ExportPackageDescription[] exported = findExportedPackages(base
335: .getBundleDescription());
336: IJavaSearchScope searchScope = PluginJavaSearchUtil
337: .createSeachScope(jProject);
338: subMonitor
339: .beginTask(
340: NLS
341: .bind(
342: PDEUIMessages.AddNewDependenciesOperation_searchForDependency,
343: pluginId),
344: exported.length);
345: for (int i = 0; i < exported.length; i++) {
346: String pkgName = exported[i].getName();
347: if (!ignorePkgs.contains(pkgName)) {
348: ReferenceFinder requestor = new ReferenceFinder();
349: engine
350: .search(
351: SearchPattern
352: .createPattern(
353: pkgName,
354: IJavaSearchConstants.PACKAGE,
355: IJavaSearchConstants.REFERENCES,
356: SearchPattern.R_EXACT_MATCH),
357: new SearchParticipant[] { SearchEngine
358: .getDefaultSearchParticipant() },
359: searchScope, requestor,
360: null);
361: if (requestor.foundMatches()) {
362: fNewDependencies = true;
363: ignorePkgs.add(pkgName);
364: newDeps.put(exported[i], pluginId);
365: if (useRequireBundle) {
366: // since using require-bundle, rest of packages will be available when bundle is added.
367: for (; i < exported.length; i++)
368: ignorePkgs.add(exported[i]
369: .getName());
370: }
371: }
372: }
373: subMonitor.worked(1);
374: }
375: }
376: subMonitor.done();
377: } catch (CoreException e) {
378: monitor.done();
379: }
380: }
381: }
382:
383: protected void addProjectPackages(IBundle bundle, Set ignorePkgs) {
384: IBuild build = getBuild();
385: if (build == null)
386: return;
387: IBuildEntry binIncludes = build
388: .getEntry(IBuildEntry.BIN_INCLUDES);
389: if (binIncludes != null) {
390: String value = bundle.getHeader(Constants.BUNDLE_CLASSPATH);
391: if (value == null)
392: value = "."; //$NON-NLS-1$
393: ManifestElement elems[];
394: try {
395: elems = ManifestElement.parseHeader(
396: Constants.BUNDLE_CLASSPATH, value);
397: } catch (BundleException e) {
398: return;
399: }
400: IJavaProject jProject = JavaCore.create(fProject);
401: for (int i = 0; i < elems.length; i++) {
402: String library = elems[i].getValue();
403: // we only want to include packages that will be avialable after exporting (ie. whatever is included in bin.includes)
404: if (binIncludes.contains(library)) {
405: // if the library is in the bin.includes, see if it is source folder that will be compile. This way we can search source folders
406: IBuildEntry entry = build
407: .getEntry(IBuildEntry.JAR_PREFIX + library);
408: if (entry != null) {
409: String[] resources = entry.getTokens();
410: for (int j = 0; j < resources.length; j++)
411: addPackagesFromResource(jProject, fProject
412: .findMember(resources[j]),
413: ignorePkgs);
414: } else {
415: // if there is no source entry for the library, assume it is a binary jar and try to add it if it exists
416: addPackagesFromResource(jProject, fProject
417: .findMember(library), ignorePkgs);
418: }
419: } else {
420: // if it is not found in the bin.includes, see if a parent folder is. This is common for binary jar.
421: StringTokenizer tokenizer = new StringTokenizer(
422: library, "/"); //$NON-NLS-1$
423: StringBuffer buffer = new StringBuffer();
424: while (tokenizer.hasMoreTokens()) {
425: buffer.append(tokenizer.nextToken())
426: .append('/');
427: if (binIncludes.contains(buffer.toString()))
428: addPackagesFromResource(jProject, fProject
429: .findMember(library), ignorePkgs);
430: }
431: }
432: }
433: }
434: }
435:
436: private void addPackagesFromResource(IJavaProject jProject,
437: IResource res, Set ignorePkgs) {
438: if (res == null)
439: return;
440: try {
441: IPackageFragmentRoot root = jProject
442: .getPackageFragmentRoot(res);
443: IJavaElement[] children = root.getChildren();
444: for (int i = 0; i < children.length; i++) {
445: String pkgName = children[i].getElementName();
446: if (children[i] instanceof IParent)
447: if (pkgName.length() > 0
448: && ((IParent) children[i]).hasChildren())
449: ignorePkgs.add(children[i].getElementName());
450: }
451: } catch (JavaModelException e) {
452: }
453: }
454:
455: protected void handleNewDependencies(final Map additionalDeps,
456: final boolean useRequireBundle, IProgressMonitor monitor) {
457: if (!additionalDeps.isEmpty())
458: addDependencies(additionalDeps, useRequireBundle);
459: monitor.done();
460: }
461:
462: protected void addDependencies(final Map depsToAdd,
463: boolean useRequireBundle) {
464: if (useRequireBundle) {
465: Collection plugins = depsToAdd.values();
466: minimizeBundles(plugins);
467: IBuild build = getBuild();
468: IPluginBase pbase = fBase.getPluginBase();
469: if (pbase == null) {
470: addRequireBundles(plugins, fBase.getBundleModel()
471: .getBundle(), build
472: .getEntry(IBuildEntry.SECONDARY_DEPENDENCIES));
473: } else
474: addRequireBundles(plugins, pbase, build
475: .getEntry(IBuildEntry.SECONDARY_DEPENDENCIES));
476: try {
477: build
478: .write(
479: "", new PrintWriter(new FileOutputStream(fProject.getFile("build.properties").getFullPath().toFile()))); //$NON-NLS-1$ //$NON-NLS-2$
480: } catch (FileNotFoundException e) {
481: }
482: } else {
483: Collection pkgs = depsToAdd.keySet();
484: addImportPackages(pkgs, fBase.getBundleModel().getBundle());
485: }
486: }
487:
488: protected final void addImportPackages(final Collection depsToAdd,
489: final IBundle bundle) {
490: Iterator it = depsToAdd.iterator();
491: IManifestHeader mheader = bundle
492: .getManifestHeader(Constants.IMPORT_PACKAGE);
493: // always create header. When available, ImportPackageHeader will help with formatting (see bug 149976)
494: if (mheader == null) {
495: bundle.setHeader(Constants.IMPORT_PACKAGE, new String());
496: mheader = bundle
497: .getManifestHeader(Constants.IMPORT_PACKAGE);
498: }
499: if (mheader instanceof ImportPackageHeader) {
500: ImportPackageHeader header = (ImportPackageHeader) mheader;
501: String versionAttr = (BundlePluginBase
502: .getBundleManifestVersion(bundle) < 2) ? ICoreConstants.PACKAGE_SPECIFICATION_VERSION
503: : Constants.VERSION_ATTRIBUTE;
504: while (it.hasNext()) {
505: ImportPackageObject obj = new ImportPackageObject(
506: header, (ExportPackageDescription) it.next(),
507: versionAttr);
508: header.addPackage(obj);
509: }
510: } else {
511: String currentValue = (mheader != null) ? mheader
512: .getValue() : null;
513: StringBuffer buffer = (currentValue == null) ? new StringBuffer()
514: : new StringBuffer(currentValue).append(", "); //$NON-NLS-1$
515: while (it.hasNext()) {
516: ExportPackageDescription desc = (ExportPackageDescription) it
517: .next();
518: String value = (desc.getVersion()
519: .equals(Version.emptyVersion)) ? desc.getName()
520: : desc.getName()
521: + "; version=\"" + desc.getVersion() + "\""; //$NON-NLS-1$ //$NON-NLS-2$
522: // use same separator as used when writing out Manifest
523: buffer.append(value).append(
524: PluginConverter.LIST_SEPARATOR);
525: }
526: if (buffer.length() > 0)
527: buffer.setLength(buffer.length()
528: - PluginConverter.LIST_SEPARATOR.length());
529: bundle.setHeader(Constants.IMPORT_PACKAGE, buffer
530: .toString());
531: }
532: }
533:
534: protected final void addRequireBundles(final Collection depsToAdd,
535: final IBundle bundle, IBuildEntry entry) {
536: if (bundle == null)
537: return;
538: HashSet added = new HashSet();
539: Iterator it = depsToAdd.iterator();
540: IManifestHeader mheader = bundle
541: .getManifestHeader(Constants.REQUIRE_BUNDLE);
542: if (mheader instanceof RequireBundleHeader) {
543: RequireBundleHeader header = (RequireBundleHeader) mheader;
544: while (it.hasNext()) {
545: String pluginId = (String) it.next();
546: if (!added.contains(pluginId))
547: try {
548: header.addBundle(pluginId);
549: added.add(pluginId);
550: entry.removeToken(pluginId);
551: } catch (CoreException e) {
552: }
553: }
554: } else {
555: String currentValue = (mheader != null) ? mheader
556: .getValue() : null;
557: StringBuffer buffer = (currentValue == null) ? new StringBuffer()
558: : new StringBuffer(currentValue).append(", "); //$NON-NLS-1$
559: while (it.hasNext()) {
560: String pluginId = (String) it.next();
561: if (!added.contains(pluginId))
562: try {
563: buffer.append(pluginId).append(
564: PluginConverter.LIST_SEPARATOR);
565: added.add(pluginId);
566: entry.removeToken(pluginId);
567: } catch (CoreException e) {
568: }
569: }
570: if (buffer.length() > 0)
571: buffer.setLength(buffer.length()
572: - PluginConverter.LIST_SEPARATOR.length());
573: bundle.setHeader(Constants.REQUIRE_BUNDLE, buffer
574: .toString());
575: }
576: }
577:
578: protected final void addRequireBundles(final Collection depsToAdd,
579: final IPluginBase base, IBuildEntry entry) {
580: HashSet added = new HashSet();
581: Iterator it = depsToAdd.iterator();
582: // must call getImports to initialize IPluginBase. Otherwise the .add(plugin) will not trigger a modification event.
583: base.getImports();
584: while (it.hasNext()) {
585: String pluginId = (String) it.next();
586: if (!added.contains(pluginId))
587: try {
588: PluginImport plugin = new PluginImport();
589: ManifestElement element = ManifestElement
590: .parseHeader(Constants.REQUIRE_BUNDLE,
591: pluginId)[0];
592: plugin.load(element, 1);
593: plugin.setModel(base.getModel());
594: base.add(plugin);
595: added.add(pluginId);
596: if (entry != null && entry.contains(pluginId))
597: entry.removeToken(pluginId);
598: } catch (BundleException e) {
599: } catch (CoreException e) {
600: }
601: }
602: }
603:
604: protected final void minimizeBundles(Collection pluginIds) {
605: Stack stack = new Stack();
606: Iterator it = pluginIds.iterator();
607: while (it.hasNext())
608: stack.push(it.next().toString());
609:
610: while (!stack.isEmpty()) {
611: IPluginModelBase base = PluginRegistry.findModel(stack
612: .pop().toString());
613: if (base == null)
614: continue;
615: IPluginImport[] imports = base.getPluginBase().getImports();
616:
617: for (int j = 0; j < imports.length; j++)
618: if (imports[j].isReexported()) {
619: String reExportedId = imports[j].getId();
620: pluginIds.remove(imports[j].getId());
621: stack.push(reExportedId);
622: }
623: }
624: }
625: }
|