/platform/lang-impl/src/com/intellij/psi/impl/source/resolve/reference/impl/providers/FileReference.java

https://bitbucket.org/nbargnesi/idea · Java · 593 lines · 496 code · 71 blank · 26 comment · 132 complexity · 5ba8ec5fbba7c02ffebfed9451c016b2 MD5 · raw file

  1. /*
  2. * Copyright 2000-2012 JetBrains s.r.o.
  3. *
  4. * Licensed under the Apache License, Version 2.0 (the "License");
  5. * you may not use this file except in compliance with the License.
  6. * You may obtain a copy of the License at
  7. *
  8. * http://www.apache.org/licenses/LICENSE-2.0
  9. *
  10. * Unless required by applicable law or agreed to in writing, software
  11. * distributed under the License is distributed on an "AS IS" BASIS,
  12. * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
  13. * See the License for the specific language governing permissions and
  14. * limitations under the License.
  15. */
  16. package com.intellij.psi.impl.source.resolve.reference.impl.providers;
  17. import com.intellij.codeInsight.daemon.EmptyResolveMessageProvider;
  18. import com.intellij.codeInsight.daemon.QuickFixProvider;
  19. import com.intellij.codeInsight.daemon.impl.HighlightInfo;
  20. import com.intellij.codeInsight.lookup.LookupElementBuilder;
  21. import com.intellij.codeInspection.LocalQuickFix;
  22. import com.intellij.codeInspection.LocalQuickFixProvider;
  23. import com.intellij.lang.LangBundle;
  24. import com.intellij.lang.injection.InjectedLanguageManager;
  25. import com.intellij.openapi.diagnostic.Logger;
  26. import com.intellij.openapi.project.Project;
  27. import com.intellij.openapi.util.Comparing;
  28. import com.intellij.openapi.util.Iconable;
  29. import com.intellij.openapi.util.TextRange;
  30. import com.intellij.openapi.util.text.StringUtil;
  31. import com.intellij.openapi.vfs.VfsUtilCore;
  32. import com.intellij.openapi.vfs.VirtualFile;
  33. import com.intellij.openapi.vfs.VirtualFileSystem;
  34. import com.intellij.openapi.vfs.newvfs.NewVirtualFileSystem;
  35. import com.intellij.psi.*;
  36. import com.intellij.psi.impl.source.resolve.ResolveCache;
  37. import com.intellij.psi.impl.source.resolve.reference.impl.CachingReference;
  38. import com.intellij.psi.search.PsiElementProcessor;
  39. import com.intellij.psi.search.PsiFileSystemItemProcessor;
  40. import com.intellij.psi.util.PsiUtilCore;
  41. import com.intellij.refactoring.rename.BindablePsiReference;
  42. import com.intellij.util.ArrayUtil;
  43. import com.intellij.util.CommonProcessors;
  44. import com.intellij.util.FilteringProcessor;
  45. import com.intellij.util.IncorrectOperationException;
  46. import gnu.trove.THashSet;
  47. import gnu.trove.TObjectHashingStrategy;
  48. import org.jetbrains.annotations.NotNull;
  49. import org.jetbrains.annotations.Nullable;
  50. import javax.swing.*;
  51. import java.net.URI;
  52. import java.util.ArrayList;
  53. import java.util.Collection;
  54. import java.util.List;
  55. /**
  56. * @author cdr
  57. */
  58. public class FileReference implements FileReferenceOwner, PsiPolyVariantReference,
  59. QuickFixProvider<FileReference>, LocalQuickFixProvider,
  60. EmptyResolveMessageProvider, BindablePsiReference {
  61. private static final Logger LOG = Logger.getInstance("#com.intellij.psi.impl.source.resolve.reference.impl.providers.FileReference");
  62. public static final FileReference[] EMPTY = new FileReference[0];
  63. private static final TObjectHashingStrategy<PsiElement> VARIANTS_HASHING_STRATEGY = new TObjectHashingStrategy<PsiElement>() {
  64. @Override
  65. public int computeHashCode(final PsiElement object) {
  66. if (object instanceof PsiNamedElement) {
  67. final String name = ((PsiNamedElement)object).getName();
  68. if (name != null) {
  69. return name.hashCode();
  70. }
  71. }
  72. return object.hashCode();
  73. }
  74. @Override
  75. public boolean equals(final PsiElement o1, final PsiElement o2) {
  76. if (o1 instanceof PsiNamedElement && o2 instanceof PsiNamedElement) {
  77. return Comparing.equal(((PsiNamedElement)o1).getName(), ((PsiNamedElement)o2).getName());
  78. }
  79. return o1.equals(o2);
  80. }
  81. };
  82. private final int myIndex;
  83. private TextRange myRange;
  84. private final String myText;
  85. @NotNull private final FileReferenceSet myFileReferenceSet;
  86. public FileReference(@NotNull final FileReferenceSet fileReferenceSet, TextRange range, int index, String text) {
  87. myFileReferenceSet = fileReferenceSet;
  88. myIndex = index;
  89. myRange = range;
  90. myText = text;
  91. }
  92. public FileReference(final FileReference original) {
  93. this(original.myFileReferenceSet, original.myRange, original.myIndex, original.myText);
  94. }
  95. @NotNull
  96. protected Collection<PsiFileSystemItem> getContexts() {
  97. final FileReference contextRef = getContextReference();
  98. ArrayList<PsiFileSystemItem> result = new ArrayList<PsiFileSystemItem>();
  99. if (contextRef == null) {
  100. Collection<PsiFileSystemItem> defaultContexts = myFileReferenceSet.getDefaultContexts();
  101. for (PsiFileSystemItem context : defaultContexts) {
  102. LOG.assertTrue(context != null, myFileReferenceSet.getClass() + " provided a null context");
  103. }
  104. result.addAll(defaultContexts);
  105. } else {
  106. ResolveResult[] resolveResults = contextRef.multiResolve(false);
  107. for (ResolveResult resolveResult : resolveResults) {
  108. if (resolveResult.getElement() != null) {
  109. result.add((PsiFileSystemItem)resolveResult.getElement());
  110. }
  111. }
  112. }
  113. result.addAll(myFileReferenceSet.getExtraContexts());
  114. return result;
  115. }
  116. @Override
  117. @NotNull
  118. public ResolveResult[] multiResolve(final boolean incompleteCode) {
  119. return ResolveCache.getInstance(getElement().getProject()).resolveWithCaching(this, MyResolver.INSTANCE, false, false);
  120. }
  121. protected ResolveResult[] innerResolve() {
  122. return innerResolve(getFileReferenceSet().isCaseSensitive());
  123. }
  124. @NotNull
  125. protected ResolveResult[] innerResolve(boolean caseSensitive) {
  126. final String referenceText = getText();
  127. if (referenceText.isEmpty() && myIndex == 0) {
  128. return new ResolveResult[] { new PsiElementResolveResult(getElement().getContainingFile())};
  129. }
  130. final Collection<PsiFileSystemItem> contexts = getContexts();
  131. final Collection<ResolveResult> result = new THashSet<ResolveResult>();
  132. for (final PsiFileSystemItem context : contexts) {
  133. if (context != null) {
  134. innerResolveInContext(referenceText, context, result, caseSensitive);
  135. }
  136. }
  137. if (contexts.size() == 0 && isAllowedEmptyPath(referenceText)) {
  138. result.add(new PsiElementResolveResult(getElement().getContainingFile()));
  139. }
  140. final int resultCount = result.size();
  141. return resultCount > 0 ? result.toArray(new ResolveResult[resultCount]) : ResolveResult.EMPTY_ARRAY;
  142. }
  143. protected void innerResolveInContext(@NotNull final String text, @NotNull final PsiFileSystemItem context, final Collection<ResolveResult> result,
  144. final boolean caseSensitive) {
  145. if (isAllowedEmptyPath(text) || ".".equals(text) || "/".equals(text)) {
  146. result.add(new PsiElementResolveResult(context));
  147. }
  148. else if ("..".equals(text)) {
  149. final PsiFileSystemItem resolved = context.getParent();
  150. if (resolved != null) {
  151. result.add(new PsiElementResolveResult(resolved));
  152. }
  153. }
  154. else {
  155. final int separatorIndex = text.indexOf('/');
  156. if (separatorIndex >= 0) {
  157. final List<ResolveResult> resolvedContexts = new ArrayList<ResolveResult>();
  158. if (separatorIndex == 0 /*starts with slash*/ && "/".equals(context.getName())) {
  159. resolvedContexts.add(new PsiElementResolveResult(context));
  160. }
  161. else {
  162. innerResolveInContext(text.substring(0, separatorIndex), context, resolvedContexts, caseSensitive);
  163. }
  164. final String restOfText = text.substring(separatorIndex + 1);
  165. for (ResolveResult contextVariant : resolvedContexts) {
  166. final PsiFileSystemItem item = (PsiFileSystemItem)contextVariant.getElement();
  167. if (item != null) {
  168. innerResolveInContext(restOfText, item, result, caseSensitive);
  169. }
  170. }
  171. }
  172. else {
  173. final String decoded = decode(text);
  174. if (decoded != null) {
  175. if (context instanceof PsiDirectory && caseSensitivityApplies((PsiDirectory)context, caseSensitive)) {
  176. // optimization: do not load all children into VFS
  177. PsiDirectory directory = (PsiDirectory)context;
  178. PsiFileSystemItem child = directory.findFile(decoded);
  179. if (child == null) child = directory.findSubdirectory(decoded);
  180. if (child != null) {
  181. result.add(new PsiElementResolveResult(getOriginalFile(child)));
  182. }
  183. }
  184. else {
  185. processVariants(context, new PsiFileSystemItemProcessor() {
  186. @Override
  187. public boolean acceptItem(String name, boolean isDirectory) {
  188. return caseSensitive ? decoded.equals(name) : decoded.compareToIgnoreCase(name) == 0;
  189. }
  190. @Override
  191. public boolean execute(@NotNull PsiFileSystemItem element) {
  192. result.add(new PsiElementResolveResult(getOriginalFile(element)));
  193. return true;
  194. }
  195. });
  196. }
  197. }
  198. }
  199. }
  200. }
  201. public String getFileNameToCreate() {
  202. return getCanonicalText();
  203. }
  204. public @Nullable String getNewFileTemplateName() {
  205. return null;
  206. }
  207. private static boolean caseSensitivityApplies(PsiDirectory context, boolean caseSensitive) {
  208. VirtualFileSystem fs = context.getVirtualFile().getFileSystem();
  209. return fs instanceof NewVirtualFileSystem && ((NewVirtualFileSystem)fs).isCaseSensitive() == caseSensitive;
  210. }
  211. private boolean isAllowedEmptyPath(String text) {
  212. return text.length() == 0 && isLast() &&
  213. (StringUtil.isEmpty(myFileReferenceSet.getPathString()) && myFileReferenceSet.isEmptyPathAllowed() ||
  214. !myFileReferenceSet.isEndingSlashNotAllowed() && myIndex > 0);
  215. }
  216. @Nullable
  217. public String decode(final String text) {
  218. // strip http get parameters
  219. String _text = text;
  220. if (text.indexOf('?') >= 0) {
  221. _text = text.substring(0, text.lastIndexOf('?'));
  222. }
  223. if (myFileReferenceSet.isUrlEncoded()) {
  224. try {
  225. return new URI(_text).getPath();
  226. }
  227. catch (Exception e) {
  228. return text;
  229. }
  230. }
  231. return _text;
  232. }
  233. @Override
  234. @NotNull
  235. public Object[] getVariants() {
  236. final String s = getText();
  237. if (s != null && s.equals("/")) {
  238. return ArrayUtil.EMPTY_OBJECT_ARRAY;
  239. }
  240. final CommonProcessors.CollectUniquesProcessor<PsiFileSystemItem> collector = new CommonProcessors.CollectUniquesProcessor<PsiFileSystemItem>();
  241. final PsiElementProcessor<PsiFileSystemItem> processor = new PsiElementProcessor<PsiFileSystemItem>() {
  242. @Override
  243. public boolean execute(@NotNull PsiFileSystemItem fileSystemItem) {
  244. return new FilteringProcessor<PsiFileSystemItem>(myFileReferenceSet.getReferenceCompletionFilter(), collector).process(getOriginalFile(fileSystemItem));
  245. }
  246. };
  247. for (PsiFileSystemItem context : getContexts()) {
  248. for (final PsiElement child : context.getChildren()) {
  249. if (child instanceof PsiFileSystemItem) {
  250. processor.execute((PsiFileSystemItem)child);
  251. }
  252. }
  253. }
  254. final THashSet<PsiElement> set = new THashSet<PsiElement>(collector.getResults(), VARIANTS_HASHING_STRATEGY);
  255. final PsiElement[] candidates = PsiUtilCore.toPsiElementArray(set);
  256. final Object[] variants = new Object[candidates.length];
  257. for (int i = 0; i < candidates.length; i++) {
  258. variants[i] = createLookupItem(candidates[i]);
  259. }
  260. if (!myFileReferenceSet.isUrlEncoded()) {
  261. return variants;
  262. }
  263. List<Object> encodedVariants = new ArrayList<Object>(variants.length);
  264. for (int i = 0; i < candidates.length; i++) {
  265. final PsiElement element = candidates[i];
  266. if (element instanceof PsiNamedElement) {
  267. final PsiNamedElement psiElement = (PsiNamedElement)element;
  268. String name = psiElement.getName();
  269. final String encoded = encode(name, psiElement);
  270. if (encoded == null) continue;
  271. if (!encoded.equals(name)) {
  272. final Icon icon = psiElement.getIcon(Iconable.ICON_FLAG_READ_STATUS | Iconable.ICON_FLAG_VISIBILITY);
  273. LookupElementBuilder item = FileInfoManager.getFileLookupItem(candidates[i], encoded, icon);
  274. encodedVariants.add(item.withTailText(" (" + name + ")"));
  275. }
  276. else {
  277. encodedVariants.add(variants[i]);
  278. }
  279. }
  280. }
  281. return ArrayUtil.toObjectArray(encodedVariants);
  282. }
  283. protected Object createLookupItem(PsiElement candidate) {
  284. return FileInfoManager.getFileLookupItem(candidate);
  285. }
  286. /**
  287. * Converts a wrapper like WebDirectoryElement into plain PsiFile
  288. */
  289. protected static PsiFileSystemItem getOriginalFile(PsiFileSystemItem fileSystemItem) {
  290. final VirtualFile file = fileSystemItem.getVirtualFile();
  291. if (file != null && !file.isDirectory()) {
  292. final PsiManager psiManager = fileSystemItem.getManager();
  293. if (psiManager != null) {
  294. final PsiFile psiFile = psiManager.findFile(file);
  295. if (psiFile != null) {
  296. fileSystemItem = psiFile;
  297. }
  298. }
  299. }
  300. return fileSystemItem;
  301. }
  302. @Nullable
  303. protected String encode(final String name, PsiElement psiElement) {
  304. try {
  305. return new URI(null, null, name, null).toString();
  306. }
  307. catch (Exception e) {
  308. return name;
  309. }
  310. }
  311. protected static void processVariants(final PsiFileSystemItem context, final PsiFileSystemItemProcessor processor) {
  312. context.processChildren(processor);
  313. }
  314. @Nullable
  315. private FileReference getContextReference() {
  316. return myIndex > 0 ? myFileReferenceSet.getReference(myIndex - 1) : null;
  317. }
  318. @Override
  319. public PsiElement getElement() {
  320. return myFileReferenceSet.getElement();
  321. }
  322. @Override
  323. public PsiFileSystemItem resolve() {
  324. ResolveResult[] resolveResults = multiResolve(false);
  325. return resolveResults.length == 1 ? (PsiFileSystemItem)resolveResults[0].getElement() : null;
  326. }
  327. @Nullable
  328. public PsiFileSystemItem innerSingleResolve(final boolean caseSensitive) {
  329. final ResolveResult[] resolveResults = innerResolve(caseSensitive);
  330. return resolveResults.length == 1 ? (PsiFileSystemItem)resolveResults[0].getElement() : null;
  331. }
  332. @Override
  333. public boolean isReferenceTo(PsiElement element) {
  334. if (!(element instanceof PsiFileSystemItem)) return false;
  335. final PsiFileSystemItem item = resolve();
  336. return item != null && FileReferenceHelperRegistrar.areElementsEquivalent(item, (PsiFileSystemItem)element);
  337. }
  338. @Override
  339. public TextRange getRangeInElement() {
  340. return myRange;
  341. }
  342. @Override
  343. @NotNull
  344. public String getCanonicalText() {
  345. return myText;
  346. }
  347. public String getText() {
  348. return myText;
  349. }
  350. @Override
  351. public boolean isSoft() {
  352. return myFileReferenceSet.isSoft();
  353. }
  354. @Override
  355. public PsiElement handleElementRename(String newElementName) throws IncorrectOperationException {
  356. final ElementManipulator<PsiElement> manipulator = CachingReference.getManipulator(getElement());
  357. myFileReferenceSet.setElement(manipulator.handleContentChange(getElement(), getRangeInElement(), newElementName));
  358. //Correct ranges
  359. int delta = newElementName.length() - myRange.getLength();
  360. myRange = new TextRange(getRangeInElement().getStartOffset(), getRangeInElement().getStartOffset() + newElementName.length());
  361. FileReference[] references = myFileReferenceSet.getAllReferences();
  362. for (int idx = myIndex + 1; idx < references.length; idx++) {
  363. references[idx].myRange = references[idx].myRange.shiftRight(delta);
  364. }
  365. return myFileReferenceSet.getElement();
  366. }
  367. public PsiElement bindToElement(@NotNull final PsiElement element, final boolean absolute) throws IncorrectOperationException {
  368. if (!(element instanceof PsiFileSystemItem)) throw new IncorrectOperationException("Cannot bind to element, should be instanceof PsiFileSystemItem: " + element);
  369. // handle empty reference that resolves to current file
  370. if (getCanonicalText().isEmpty() && element == getElement().getContainingFile()) return getElement();
  371. final PsiFileSystemItem fileSystemItem = (PsiFileSystemItem)element;
  372. VirtualFile dstVFile = fileSystemItem.getVirtualFile();
  373. if (dstVFile == null) throw new IncorrectOperationException("Cannot bind to non-physical element:" + element);
  374. PsiFile file = getElement().getContainingFile();
  375. PsiElement contextPsiFile = InjectedLanguageManager.getInstance(file.getProject()).getInjectionHost(file);
  376. if (contextPsiFile != null) file = contextPsiFile.getContainingFile(); // use host file!
  377. final VirtualFile curVFile = file.getVirtualFile();
  378. if (curVFile == null) throw new IncorrectOperationException("Cannot bind from non-physical element:" + file);
  379. final Project project = element.getProject();
  380. String newName;
  381. if (absolute) {
  382. PsiFileSystemItem root = null;
  383. PsiFileSystemItem dstItem = null;
  384. for (final FileReferenceHelper helper : FileReferenceHelperRegistrar.getHelpers()) {
  385. if (!helper.isMine(project, dstVFile)) continue;
  386. PsiFileSystemItem _dstItem = helper.getPsiFileSystemItem(project, dstVFile);
  387. if (_dstItem != null) {
  388. PsiFileSystemItem _root = helper.findRoot(project, dstVFile);
  389. if (_root != null) {
  390. root = _root;
  391. dstItem = _dstItem;
  392. break;
  393. }
  394. }
  395. }
  396. if (root == null) {
  397. PsiFileSystemItem _dstItem = FileReferenceHelperRegistrar.NullFileReferenceHelper.INSTANCE.getPsiFileSystemItem(project, dstVFile);
  398. if (_dstItem != null) {
  399. PsiFileSystemItem _root = FileReferenceHelperRegistrar.NullFileReferenceHelper.INSTANCE.findRoot(project, dstVFile);
  400. if (_root != null) {
  401. root = _root;
  402. dstItem = _dstItem;
  403. }
  404. }
  405. if (root == null) {
  406. return getElement();
  407. }
  408. }
  409. final String relativePath = PsiFileSystemItemUtil.getRelativePath(root, dstItem);
  410. if (relativePath == null) {
  411. return getElement();
  412. }
  413. newName = myFileReferenceSet.getNewAbsolutePath(root, relativePath);
  414. } else { // relative path
  415. PsiFileSystemItem curItem = null;
  416. PsiFileSystemItem dstItem = null;
  417. final FileReferenceHelper helper = FileReferenceHelperRegistrar.getNotNullHelper(file);
  418. final Collection<PsiFileSystemItem> contexts = helper.getContexts(project, curVFile);
  419. switch (contexts.size()) {
  420. case 0:
  421. break;
  422. default:
  423. for (PsiFileSystemItem context : contexts) {
  424. final VirtualFile contextFile = context.getVirtualFile();
  425. assert contextFile != null;
  426. if (VfsUtilCore.isAncestor(contextFile, dstVFile, true)) {
  427. final String path = VfsUtilCore.getRelativePath(dstVFile, contextFile, '/');
  428. if (path != null) {
  429. return rename(path);
  430. }
  431. }
  432. }
  433. case 1:
  434. PsiFileSystemItem _dstItem = helper.getPsiFileSystemItem(project, dstVFile);
  435. PsiFileSystemItem _curItem = helper.getPsiFileSystemItem(project, curVFile);
  436. if (_dstItem != null && _curItem != null) {
  437. curItem = _curItem;
  438. dstItem = _dstItem;
  439. break;
  440. }
  441. }
  442. if (curItem == null) {
  443. throw new IncorrectOperationException("Cannot find path between files; " +
  444. "src = " + curVFile.getPresentableUrl() + "; " +
  445. "dst = " + dstVFile.getPresentableUrl() + "; " +
  446. "Contexts: " + contexts);
  447. }
  448. if (curItem.equals(dstItem)) {
  449. if (getCanonicalText().equals(dstItem.getName())) {
  450. return getElement();
  451. }
  452. return fixRefText(file.getName());
  453. }
  454. newName = PsiFileSystemItemUtil.getRelativePath(curItem, dstItem);
  455. if (newName == null) {
  456. return getElement();
  457. }
  458. }
  459. if (myFileReferenceSet.isUrlEncoded()) {
  460. newName = encode(newName, element);
  461. }
  462. return rename(newName);
  463. }
  464. protected PsiElement fixRefText(String name) {
  465. return ElementManipulators.getManipulator(getElement()).handleContentChange(getElement(), getRangeInElement(), name);
  466. }
  467. /* Happens when it's been moved to another folder */
  468. @Override
  469. public PsiElement bindToElement(@NotNull final PsiElement element) throws IncorrectOperationException {
  470. return bindToElement(element, myFileReferenceSet.isAbsolutePathReference());
  471. }
  472. protected PsiElement rename(final String newName) throws IncorrectOperationException {
  473. final TextRange range = new TextRange(myFileReferenceSet.getStartInElement(), getRangeInElement().getEndOffset());
  474. PsiElement element = getElement();
  475. return CachingReference.getManipulator(element).handleContentChange(element, range, newName);
  476. }
  477. @Override
  478. public void registerQuickfix(HighlightInfo info, FileReference reference) {
  479. for (final FileReferenceHelper helper : getHelpers()) {
  480. helper.registerFixes(info, reference);
  481. }
  482. }
  483. protected static FileReferenceHelper[] getHelpers() {
  484. return FileReferenceHelperRegistrar.getHelpers();
  485. }
  486. public int getIndex() {
  487. return myIndex;
  488. }
  489. @Override
  490. public String getUnresolvedMessagePattern() {
  491. return LangBundle.message("error.cannot.resolve")
  492. + " " + (isLast() ? LangBundle.message("terms.file") : LangBundle.message("terms.directory"))
  493. + " '" + StringUtil.escapePattern(StringUtil.notNullize(decode(getCanonicalText()))) + "'";
  494. }
  495. public final boolean isLast() {
  496. return myIndex == myFileReferenceSet.getAllReferences().length - 1;
  497. }
  498. @NotNull
  499. public FileReferenceSet getFileReferenceSet() {
  500. return myFileReferenceSet;
  501. }
  502. @Override
  503. public LocalQuickFix[] getQuickFixes() {
  504. final List<LocalQuickFix> result = new ArrayList<LocalQuickFix>();
  505. for (final FileReferenceHelper helper : getHelpers()) {
  506. result.addAll(helper.registerFixes(null, this));
  507. }
  508. return result.toArray(new LocalQuickFix[result.size()]);
  509. }
  510. @Override
  511. public FileReference getLastFileReference() {
  512. return myFileReferenceSet.getLastReference();
  513. }
  514. static class MyResolver implements ResolveCache.PolyVariantResolver<FileReference> {
  515. static final MyResolver INSTANCE = new MyResolver();
  516. @NotNull
  517. @Override
  518. public ResolveResult[] resolve(@NotNull FileReference ref, boolean incompleteCode) {
  519. return ref.innerResolve(ref.getFileReferenceSet().isCaseSensitive());
  520. }
  521. }
  522. }