1 package net.sourceforge.phpeclipse.builder;
3 import java.io.BufferedInputStream;
4 import java.io.BufferedReader;
5 import java.io.FileNotFoundException;
6 import java.io.FileReader;
7 import java.io.FileWriter;
8 import java.io.IOException;
9 import java.io.InputStream;
10 import java.util.ArrayList;
11 import java.util.Collection;
12 import java.util.Comparator;
13 import java.util.HashMap;
14 import java.util.Iterator;
15 import java.util.List;
17 import java.util.SortedMap;
18 import java.util.StringTokenizer;
19 import java.util.TreeMap;
21 import net.sourceforge.phpdt.core.compiler.ITerminalSymbols;
22 import net.sourceforge.phpdt.core.compiler.InvalidInputException;
23 import net.sourceforge.phpdt.internal.compiler.parser.Scanner;
24 import net.sourceforge.phpdt.internal.compiler.parser.SyntaxError;
25 import net.sourceforge.phpdt.internal.compiler.util.Util;
26 import net.sourceforge.phpeclipse.PHPeclipsePlugin;
27 import net.sourceforge.phpeclipse.obfuscator.PHPIdentifier;
29 import org.eclipse.core.resources.IFile;
30 import org.eclipse.core.runtime.CoreException;
31 import org.eclipse.core.runtime.IStatus;
34 * Manages the identifer index information for a specific project
37 public class IdentifierIndexManager {
38 public class LineCreator implements ITerminalSymbols {
39 private Scanner fScanner;
43 public LineCreator() {
44 fScanner = new Scanner(true, false, false, false, true, null, null,
45 true /* taskCaseSensitive */);
49 * Add the information of the current identifier to the line
51 * @param typeOfIdentifier
52 * the type of the identifier ('c'lass, 'd'efine, 'f'unction,
53 * 'm'ethod(class), 'v'ariable(class) 'g'lobal variable)
57 * Buffer for the current index line
59 private void addIdentifierInformation(char typeOfIdentifier,
60 char[] identifier, StringBuffer line) {
62 line.append(typeOfIdentifier);
63 line.append(identifier);
64 // line.append("\to"); // Offset
65 // line.append(fScanner.getCurrentTokenStartPosition());
69 * Add the information of the current identifier to the line
71 * @param typeOfIdentifier
72 * the type of the identifier ('c'lass, 'd'efine, 'f'unction,
73 * 'm'ethod(class), 'v'ariable(class) 'g'lobal variable)
77 * Buffer for the current index line
79 * the offset of the PHPdoc comment if available
81 * the length of the PHPdoc comment if available
83 private void addIdentifierInformation(char typeOfIdentifier,
84 char[] identifier, StringBuffer line, int phpdocOffset,
87 line.append(typeOfIdentifier);
88 line.append(identifier);
89 line.append("\to"); // Offset
90 line.append(fScanner.getCurrentTokenStartPosition());
91 if (phpdocOffset >= 0) {
92 line.append("\tp"); // phpdoc offset
93 line.append(phpdocOffset);
94 line.append("\tl"); // phpdoc length
95 line.append(phpdocLength);
99 private void addClassVariableInformation(char typeOfIdentifier,
100 char[] identifier, StringBuffer line, int phpdocOffset,
103 line.append(typeOfIdentifier);
104 line.append(identifier);
105 line.append("\to"); // Offset
106 // we don't store the '$' in the index for class variables:
107 line.append(fScanner.getCurrentTokenStartPosition() + 1);
108 if (phpdocOffset >= 0) {
109 line.append("\tp"); // phpdoc offset
110 line.append(phpdocOffset);
111 line.append("\tl"); // phpdoc length
112 line.append(phpdocLength);
117 * Get the next token from input
119 private void getNextToken() throws InvalidInputException {
121 fToken = fScanner.getNextToken();
123 int currentEndPosition = fScanner.getCurrentTokenEndPosition();
124 int currentStartPosition = fScanner
125 .getCurrentTokenStartPosition();
126 System.out.print(currentStartPosition + ","
127 + currentEndPosition + ": ");
128 System.out.println(fScanner.toStringAction(fToken));
131 // } catch (InvalidInputException e) {
133 // // e.printStackTrace();
135 // fToken = TokenNameERROR;
138 private void parseDeclarations(char[] parent, StringBuffer buf,
141 char[] classVariable;
143 boolean hasModifiers = false;
144 int phpdocOffset = -1;
145 int phpdocLength = -1;
147 while (fToken != TokenNameEOF && fToken != TokenNameERROR) {
149 hasModifiers = false;
150 if (fToken == TokenNameCOMMENT_PHPDOC) {
151 phpdocOffset = fScanner.getCurrentTokenStartPosition();
152 phpdocLength = fScanner.getCurrentTokenEndPosition()
153 - fScanner.getCurrentTokenStartPosition() + 1;
155 while (fToken == TokenNamestatic
156 || fToken == TokenNamefinal
157 || fToken == TokenNamepublic
158 || fToken == TokenNameprotected
159 || fToken == TokenNameprivate
160 || fToken == TokenNameabstract) {
164 if (fToken == TokenNameEOF || fToken == TokenNameERROR) {
168 if (fToken == TokenNamefunction) {
170 if (fToken == TokenNameAND) {
173 if (fToken == TokenNameIdentifier) {
174 ident = fScanner.getCurrentIdentifierSource();
176 && equalCharArrays(parent, ident)) {
177 // constructor function
178 addIdentifierInformation('k', ident, buf,
179 phpdocOffset, phpdocLength);
181 if (parent != null) {
182 // class method function
183 addIdentifierInformation('m', ident, buf,
184 phpdocOffset, phpdocLength);
186 // nested function ?!
187 addIdentifierInformation('f', ident, buf,
188 phpdocOffset, phpdocLength);
192 parseDeclarations(null, buf, true);
194 } else if (fToken == TokenNameclass
195 || fToken == TokenNameinterface) {
197 if (fToken == TokenNameIdentifier) {
198 ident = fScanner.getCurrentIdentifierSource();
199 addIdentifierInformation('c', ident, buf,
200 phpdocOffset, phpdocLength);
202 if (fToken == TokenNameextends) {
204 while (fToken == TokenNameIdentifier) {
206 .getCurrentIdentifierSource();
208 addIdentifierInformation('e', ident, buf);
210 if (fToken == TokenNameCOMMA) {
215 if (fToken == TokenNameimplements) {
217 while (fToken == TokenNameIdentifier) {
219 .getCurrentIdentifierSource();
221 addIdentifierInformation('e', ident, buf);
223 if (fToken == TokenNameCOMMA) {
228 // skip tokens for classname, extends and others
231 while (fToken != TokenNameLBRACE
232 && fToken != TokenNameEOF
233 && fToken != TokenNameERROR) {
236 parseDeclarations(ident, buf, true);
238 } else if (fToken == TokenNamevar || hasModifiers
239 || fToken == TokenNamestatic
240 || fToken == TokenNamefinal
241 || fToken == TokenNamepublic
242 || fToken == TokenNameprotected
243 || fToken == TokenNameprivate) {
244 while (fToken == TokenNamevar
245 || fToken == TokenNamestatic
246 || fToken == TokenNamefinal
247 || fToken == TokenNamepublic
248 || fToken == TokenNameprotected
249 || fToken == TokenNameprivate) {
252 while (fToken == TokenNameVariable) {
253 ident = fScanner.getCurrentIdentifierSource();
254 classVariable = new char[ident.length - 1];
255 System.arraycopy(ident, 1, classVariable, 0,
257 addClassVariableInformation('v', classVariable,
258 buf, phpdocOffset, phpdocLength);
260 if (fToken == TokenNameCOMMA) {
264 } else if (!hasModifiers && fToken == TokenNameIdentifier) {
265 ident = fScanner.getCurrentIdentifierSource();
267 if (ident.length == 6 && ident[0] == 'd'
268 && ident[1] == 'e' && ident[2] == 'f'
269 && ident[3] == 'i' && ident[4] == 'n'
270 && ident[5] == 'e') {
271 if (fToken == TokenNameLPAREN) {
273 if (fToken == TokenNameStringDoubleQuote) {
275 .getCurrentStringLiteralSource();
276 addIdentifierInformation('d', ident, buf,
277 phpdocOffset, phpdocLength);
279 } else if (fToken == TokenNameStringSingleQuote) {
281 .getCurrentStringLiteralSource();
282 addIdentifierInformation('d', ident, buf,
283 phpdocOffset, phpdocLength);
288 } else if (fToken == TokenNameglobal) {
290 while (fToken != TokenNameEOF
291 && fToken != TokenNameERROR
292 && fToken != TokenNameSEMICOLON
293 && fToken != TokenNameLBRACE
294 && fToken != TokenNameRBRACE) {
296 if (fToken == TokenNameVariable) {
297 ident = fScanner.getCurrentIdentifierSource();
298 addIdentifierInformation('g', ident, buf,
299 phpdocOffset, phpdocLength);
302 } else if (fToken == TokenNameLBRACE) {
305 } else if (fToken == TokenNameRBRACE) {
308 if (counter == 0 && goBack) {
315 } catch (InvalidInputException e) {
317 } catch (SyntaxError e) {
318 // TODO Auto-generated catch block
323 synchronized public void parseIdentifiers(char[] charArray,
328 boolean hasModifiers = false;
329 int phpdocOffset = -1;
330 int phpdocLength = -1;
331 fScanner.setSource(charArray);
332 fScanner.setPHPMode(false);
333 fToken = TokenNameEOF;
336 while (fToken != TokenNameEOF) { // && fToken !=
339 hasModifiers = false;
340 if (fToken == TokenNameCOMMENT_PHPDOC) {
341 phpdocOffset = fScanner.getCurrentTokenStartPosition();
342 phpdocLength = fScanner.getCurrentTokenEndPosition()
343 - fScanner.getCurrentTokenStartPosition() + 1;
345 while (fToken == TokenNamestatic
346 || fToken == TokenNamefinal
347 || fToken == TokenNamepublic
348 || fToken == TokenNameprotected
349 || fToken == TokenNameprivate
350 || fToken == TokenNameabstract) {
354 if (fToken == TokenNameEOF || fToken == TokenNameERROR) {
358 if (fToken == TokenNamefunction) {
360 if (fToken == TokenNameAND) {
363 if (fToken == TokenNameIdentifier) {
364 ident = fScanner.getCurrentIdentifierSource();
365 addIdentifierInformation('f', ident, buf,
366 phpdocOffset, phpdocLength);
368 parseDeclarations(null, buf, true);
370 } else if (fToken == TokenNameclass
371 || fToken == TokenNameinterface) {
373 if (fToken == TokenNameIdentifier) {
374 ident = fScanner.getCurrentIdentifierSource();
375 addIdentifierInformation('c', ident, buf,
376 phpdocOffset, phpdocLength);
378 if (fToken == TokenNameextends) {
380 while (fToken == TokenNameIdentifier) {
382 .getCurrentIdentifierSource();
384 addIdentifierInformation('e', ident, buf);
386 if (fToken == TokenNameCOMMA) {
391 if (fToken == TokenNameimplements) {
393 while (fToken == TokenNameIdentifier) {
395 .getCurrentIdentifierSource();
397 addIdentifierInformation('e', ident, buf);
399 if (fToken == TokenNameCOMMA) {
404 // skip fTokens for classname, extends and others
407 while (fToken != TokenNameLBRACE
408 && fToken != TokenNameEOF
409 && fToken != TokenNameERROR) {
412 parseDeclarations(ident, buf, true);
414 } else if (fToken == TokenNameVariable) {
416 ident = fScanner.getCurrentIdentifierSource();
417 addIdentifierInformation('g', ident, buf, phpdocOffset,
420 } else if (!hasModifiers && fToken == TokenNameIdentifier) {
421 ident = fScanner.getCurrentIdentifierSource();
423 if (ident.length == 6 && ident[0] == 'd'
424 && ident[1] == 'e' && ident[2] == 'f'
425 && ident[3] == 'i' && ident[4] == 'n'
426 && ident[5] == 'e') {
427 if (fToken == TokenNameLPAREN) {
429 if (fToken == TokenNameStringDoubleQuote) {
431 .getCurrentStringLiteralSource();
432 addIdentifierInformation('d', ident, buf,
433 phpdocOffset, phpdocLength);
435 } else if (fToken == TokenNameStringSingleQuote) {
437 .getCurrentStringLiteralSource();
438 addIdentifierInformation('d', ident, buf,
439 phpdocOffset, phpdocLength);
448 } catch (InvalidInputException e) {
450 } catch (SyntaxError e) {
451 // TODO Auto-generated catch block
457 class StringComparator implements Comparator {
458 public int compare(Object o1, Object o2) {
459 String s1 = (String) o1;
460 String s2 = (String) o2;
461 return s1.compareTo(s2);
462 // return s1.toUpperCase().compareTo(s2.toUpperCase());
465 public boolean equals(Object o) {
466 String s = (String) o;
467 return compare(this, o) == 0;
471 private HashMap fFileMap;
473 private String fFilename;
475 private TreeMap fIndentifierMap;
477 public IdentifierIndexManager(String filename) {
478 fFilename = filename;
484 * Check if 2 char arrays are equal
490 private static boolean equalCharArrays(char[] a, char[] b) {
491 if (a.length != b.length) {
494 for (int i = 0; i < b.length; i++) {
502 public LineCreator createLineCreator() {
503 return new LineCreator();
507 * Add the information for a given IFile resource
510 public void addFile(IFile fileToParse) {
511 // InputStream iStream;
512 LineCreator lineCreator = createLineCreator();
514 addInputStream(new BufferedInputStream(fileToParse.getContents()),
515 fileToParse.getProjectRelativePath().toString(),
517 } catch (CoreException e1) {
518 // TODO Auto-generated catch block
519 e1.printStackTrace();
526 * @throws CoreException
528 public void addInputStream(InputStream stream, String filePath,
529 LineCreator lineCreator) throws CoreException {
531 StringBuffer lineBuffer = new StringBuffer();
532 lineBuffer.append(filePath);
533 int lineLength = lineBuffer.length();
534 lineCreator.parseIdentifiers(Util.getInputStreamAsCharArray(stream,
535 -1, null), lineBuffer);
536 // if (lineLength != lineBuffer.length()) {
537 // always add the file for Open Include Action
538 addLine(lineBuffer.toString());
540 } catch (IOException e) {
544 if (stream != null) {
547 } catch (IOException e) {
553 * Adds a line of the index file for function, class, class-method and
554 * class-variable names
558 private void addLine(String line) {
559 addLine(fIndentifierMap, fFileMap, line, null);
562 public TreeMap getIdentifiers(IFile file) {
563 TreeMap treeMap = new TreeMap(new StringComparator());
564 addIdentifiers(treeMap, file);
568 public TreeMap getIdentifiers(String startClazz) {
569 TreeMap treeMap = new TreeMap(new StringComparator());
570 addIdentifiers(treeMap, startClazz);
574 public void addIdentifiers(TreeMap treeMap, IFile file) {
575 String line = (String) fFileMap.get(file.getProjectRelativePath()
578 PHPIdentifierLocation ident;
579 ArrayList allClassNames = new ArrayList();
580 addLine(treeMap, null, line, allClassNames);
582 while (i < allClassNames.size()) {
583 String clazz = (String) allClassNames.get(i++);
584 addClassName(treeMap, clazz, allClassNames);
589 public void addIdentifiers(TreeMap treeMap, String startClazz) {
590 PHPIdentifierLocation ident;
591 ArrayList allClassNames = new ArrayList();
592 addClassName(treeMap, startClazz, allClassNames);
594 while (i < allClassNames.size()) {
595 String clazz = (String) allClassNames.get(i++);
596 addClassName(treeMap, clazz, allClassNames);
603 * @param allClassNames
605 private boolean addClassName(TreeMap treeMap, String clazz,
606 List allClassNames) {
608 PHPIdentifierLocation ident;
609 List list = getLocations(clazz);
613 boolean result = false;
614 for (int i = 0; i < list.size(); i++) {
615 ident = (PHPIdentifierLocation) list.get(i);
616 if (ident.isClass()) {
617 line = (String) fFileMap.get(ident.getFilename());
618 addLine(treeMap, null, line, allClassNames);
626 * Adds a line of the index file for function, class, class-method and
627 * class-variable names
631 public void addLine(TreeMap treeMap, HashMap fileMap, String line,
632 List allClassNames) {
633 StringTokenizer tokenizer;
634 String phpFileName = null;
636 String identifier = null;
637 String classname = null;
638 String offset = null;
639 PHPIdentifierLocation phpIdentifier = null;
640 boolean tokenExists = false;
641 tokenizer = new StringTokenizer(line, "\t");
642 // first token contains the filename:
644 if (tokenizer.hasMoreTokens()) {
645 phpFileName = tokenizer.nextToken();
646 // System.out.println(token);
650 // all the other tokens are identifiers:
651 while (tokenizer.hasMoreTokens()) {
652 token = tokenizer.nextToken();
653 // System.out.println(token);
654 switch (token.charAt(0)) {
657 identifier = token.substring(1);
658 classname = identifier;
659 phpIdentifier = new PHPIdentifierLocation(identifier,
660 PHPIdentifier.CLASS, phpFileName);
664 identifier = token.substring(1);
665 phpIdentifier = new PHPIdentifierLocation(identifier,
666 PHPIdentifier.DEFINE, phpFileName);
669 // extends <class name>
672 phpIdentifier = null;
673 if (allClassNames != null) {
674 String extName = token.substring(1);
675 if (!allClassNames.contains(extName)) {
676 allClassNames.add(extName);
682 identifier = token.substring(1);
683 phpIdentifier = new PHPIdentifierLocation(identifier,
684 PHPIdentifier.FUNCTION, phpFileName);
688 identifier = token.substring(1);
689 phpIdentifier = new PHPIdentifierLocation(identifier,
690 PHPIdentifier.GLOBAL_VARIABLE, phpFileName);
693 // implements <class name>
696 phpIdentifier = null;
697 if (allClassNames != null) {
698 String implName = token.substring(1);
699 if (!allClassNames.contains(implName)) {
700 allClassNames.add(implName);
705 // constructor function name
706 identifier = token.substring(1);
707 phpIdentifier = new PHPIdentifierLocation(identifier,
708 PHPIdentifier.CONSTRUCTOR, phpFileName);
711 // method inside a class
712 identifier = token.substring(1);
713 phpIdentifier = new PHPIdentifierLocation(identifier,
714 PHPIdentifier.METHOD, phpFileName, classname);
717 // variable inside a class
718 identifier = token.substring(1);
719 phpIdentifier = new PHPIdentifierLocation(identifier,
720 PHPIdentifier.VARIABLE, phpFileName, classname);
723 // offset information
725 if (phpIdentifier != null) {
726 offset = token.substring(1);
727 phpIdentifier.setOffset(Integer.parseInt(offset));
731 // PHPdoc offset information
733 if (phpIdentifier != null) {
734 offset = token.substring(1);
735 phpIdentifier.setPHPDocOffset(Integer.parseInt(offset));
739 // PHPdoc length information
741 if (phpIdentifier != null) {
742 offset = token.substring(1);
743 phpIdentifier.setPHPDocLength(Integer.parseInt(offset));
747 PHPeclipsePlugin.log(IStatus.ERROR,
748 "Unknown token character in IdentifierIndexManager: "
751 phpIdentifier = null;
754 if (identifier != null && phpIdentifier != null) {
756 ArrayList list = (ArrayList) treeMap.get(identifier);
758 list = new ArrayList();
759 list.add(phpIdentifier);
760 treeMap.put(identifier, list);
762 boolean flag = false;
763 for (int i = 0; i < list.size(); i++) {
764 if (list.get(i).equals(phpIdentifier)) {
770 list.add(phpIdentifier);
775 if (fileMap != null) {
776 fileMap.put(phpFileName, line);
778 } catch (Throwable e) {
779 // write to workspace/.metadata/.log file
780 PHPeclipsePlugin.log(e);
782 // if (tokenExists) {
788 * Change the information for a given IFile resource
791 public void changeFile(IFile fileToParse) {
792 removeFile(fileToParse);
793 addFile(fileToParse);
797 * Get a list of all PHPIdentifierLocation object's associated with an
803 public List getLocations(String identifier) {
804 List list = (List) fIndentifierMap.get(identifier);
808 return new ArrayList();
812 * Initialize (i.e. clear) the current index information
815 public void initialize() {
816 fIndentifierMap = new TreeMap(new StringComparator());
817 fFileMap = new HashMap();
820 private void readFile() {
821 FileReader fileReader;
823 fileReader = new FileReader(fFilename);
824 BufferedReader bufferedReader = new BufferedReader(fileReader);
826 while (bufferedReader.ready()) {
827 // all entries for one file are in a line
828 // separated by tabs !
829 line = bufferedReader.readLine();
833 } catch (FileNotFoundException e) {
835 // TODO DialogBox which asks the user if she/he likes to build new
837 } catch (IOException e) {
838 // TODO Auto-generated catch block
844 * Remove the information for a given IFile resource
847 public void removeFile(IFile fileToParse) {
848 // String line = (String)
849 // fFileMap.get(fileToParse.getLocation().toString());
850 String line = (String) fFileMap.get(fileToParse
851 .getProjectRelativePath().toString());
858 * Removes a line of the index file for function, class, class-method and
859 * class-variable names
863 private void removeLine(String line) {
864 StringTokenizer tokenizer;
865 String phpFileName = null;
867 String identifier = null;
868 String classname = null;
869 PHPIdentifier phpIdentifier = null;
870 boolean tokenExists = false;
871 tokenizer = new StringTokenizer(line, "\t");
872 // first token contains the filename:
873 if (tokenizer.hasMoreTokens()) {
874 phpFileName = tokenizer.nextToken();
875 // System.out.println(token);
880 // all the other tokens are identifiers:
881 while (tokenizer.hasMoreTokens()) {
882 token = tokenizer.nextToken();
883 // System.out.println(token);
884 switch (token.charAt(0)) {
887 identifier = token.substring(1);
888 classname = identifier;
889 phpIdentifier = new PHPIdentifierLocation(identifier,
890 PHPIdentifier.CLASS, phpFileName);
894 identifier = token.substring(1);
895 phpIdentifier = new PHPIdentifierLocation(identifier,
896 PHPIdentifier.DEFINE, phpFileName);
899 // extends <class name>
901 phpIdentifier = null;
905 identifier = token.substring(1);
906 phpIdentifier = new PHPIdentifierLocation(identifier,
907 PHPIdentifier.FUNCTION, phpFileName);
911 identifier = token.substring(1);
912 phpIdentifier = new PHPIdentifierLocation(identifier,
913 PHPIdentifier.GLOBAL_VARIABLE, phpFileName);
916 // implements <class name>
918 phpIdentifier = null;
921 // constructor function name
922 identifier = token.substring(1);
923 phpIdentifier = new PHPIdentifierLocation(identifier,
924 PHPIdentifier.CONSTRUCTOR, phpFileName);
927 // method inside a class
928 identifier = token.substring(1);
929 phpIdentifier = new PHPIdentifierLocation(identifier,
930 PHPIdentifier.METHOD, phpFileName, classname);
933 // offset information
937 // PHPdoc offset information
941 // PHPdoc length information
945 // variable inside a class
946 identifier = token.substring(1);
947 phpIdentifier = new PHPIdentifierLocation(identifier,
948 PHPIdentifier.VARIABLE, phpFileName, classname);
951 PHPeclipsePlugin.log(IStatus.ERROR,
952 "Unknown token character in IdentifierIndexManager: "
955 phpIdentifier = null;
958 if (identifier != null && phpIdentifier != null) {
959 ArrayList list = (ArrayList) fIndentifierMap.get(identifier);
962 for (int i = 0; i < list.size(); i++) {
963 if (list.get(i).equals(phpIdentifier)) {
968 if (list.size() == 0) {
969 fIndentifierMap.remove(identifier);
974 fFileMap.remove(phpFileName);
978 * Save the current index information in the projects index file
981 public void writeFile() {
982 FileWriter fileWriter;
984 fileWriter = new FileWriter(fFilename);
986 Collection collection = fFileMap.values();
987 Iterator iterator = collection.iterator();
988 while (iterator.hasNext()) {
989 line = (String) iterator.next();
990 fileWriter.write(line + '\n');
993 } catch (FileNotFoundException e) {
994 // ignore exception; project is deleted by user
995 } catch (IOException e) {
996 // TODO Auto-generated catch block
1006 public SortedMap getIdentifierMap() {
1007 return fIndentifierMap;
1010 synchronized public List getFileList(String filePattern) {
1011 Set set = fFileMap.keySet();
1012 if (set.isEmpty()) {
1015 Iterator iter = set.iterator();
1016 ArrayList list = new ArrayList();
1019 while (iter.hasNext()) {
1020 fileName = (String) iter.next();
1021 if ((index = fileName.indexOf(filePattern)) != -1
1022 && fileName.length() == (index + filePattern.length())) {