package org.eclipse.jdt.internal.core.jdom; | |
/* | |
* (c) Copyright IBM Corp. 2000, 2001. | |
* All Rights Reserved. | |
*/ | |
import org.eclipse.jdt.core.IJavaElement; | |
import org.eclipse.jdt.core.IType; | |
import org.eclipse.jdt.core.Signature; | |
import org.eclipse.jdt.core.jdom.IDOMMethod; | |
import org.eclipse.jdt.core.jdom.IDOMNode; | |
import org.eclipse.jdt.internal.core.JavaModelManager; | |
import org.eclipse.jdt.internal.core.Util; | |
import org.eclipse.jdt.internal.core.util.CharArrayBuffer; | |
import org.eclipse.jdt.internal.core.util.CharArrayOps; | |
/** | |
* DOMMethod provides an implementation of IDOMMethod. | |
* | |
* @see IDOMMethod | |
* @see DOMNode | |
*/ | |
class DOMMethod extends DOMMember implements IDOMMethod { | |
/** | |
* Contains the return type of the method when the | |
* return type has been altered from the contents | |
* in the document, otherwise <code>null</code>. | |
*/ | |
protected String fReturnType; | |
/** | |
* The original inclusive source range of the | |
* method's return type in the document, or -1's | |
* if no return type is present in the document. | |
* If the return type of this method is qualified with | |
* '[]' following the parameter list, this array has | |
* four entries. In this case, the last two entries | |
* of the array are the inclusive source range of | |
* the array qualifiers. | |
*/ | |
protected int[] fReturnTypeRange; | |
/** | |
* Contains the textual representation of the method's | |
* parameter list, including open and closing parentheses | |
* when the parameters had been altered from the contents | |
* in the document, otherwise <code>null</code>. | |
*/ | |
protected char[] fParameterList; | |
/** | |
* The original inclusive source range of the | |
* method's parameter list in the document. | |
*/ | |
protected int[] fParameterRange; | |
/** | |
* Contains the textual representation of the method's | |
* exception list when the exceptions had been altered | |
* from the contents in the document, otherwise | |
* <code>null</code>. The exception list is a comment | |
* delimited list of exceptions, not including the "throws" | |
* keyword. | |
*/ | |
protected char[] fExceptionList; | |
/** | |
* The original inclusive source range of the | |
* method's exception list in the document. | |
*/ | |
protected int[] fExceptionRange; | |
/** | |
* Contains the method's body when the body has | |
* been altered from the contents in the document, | |
* otherwise <code>null</code>. The body includes everything | |
* between and including the enclosing braces, and trailing | |
* whitespace. | |
*/ | |
protected String fBody; | |
/** | |
* The original inclusive source range of the | |
* method's body. | |
*/ | |
protected int[] fBodyRange; | |
/** | |
* Names of parameters in the method parameter list, | |
* or <code>null</code> if the method has no parameters. | |
*/ | |
protected String[] fParameterNames; | |
/** | |
* Types of parameters in the method parameter list, | |
* or <code>null</code> if the method has no parameters. | |
*/ | |
protected String[] fParameterTypes; | |
/** | |
* The exceptions the method throws, or <code>null</code> | |
* if the method throws no exceptions. | |
*/ | |
protected String[] fExceptions; | |
/** | |
* Constructs an empty method node. | |
*/ | |
DOMMethod() { | |
} | |
/** | |
* Creates a new detailed METHOD document fragment on the given range of the document. | |
* | |
* @param document - the document containing this node's original contents | |
* @param sourceRange - a two element array of integers describing the | |
* entire inclusive source range of this node within its document. | |
* Contents start on and include the character at the first position. | |
* Contents end on and include the character at the last position. | |
* An array of -1's indicates this node's contents do not exist | |
* in the document. | |
* @param name - the identifier portion of the name of this node, or | |
* <code>null</code> if this node does not have a name | |
* @param nameRange - a two element array of integers describing the | |
* entire inclusive source range of this node's name within its document, | |
* including any array qualifiers that might immediately follow the name | |
* or -1's if this node does not have a name. | |
* @param commentRange - a two element array describing the comments that precede | |
* the member declaration. The first matches the start of this node's | |
* sourceRange, and the second is the new-line or first non-whitespace | |
* character following the last comment. If no comments are present, | |
* this array contains two -1's. | |
* @param flags - an integer representing the modifiers for this member. The | |
* integer can be analyzed with org.eclipse.jdt.core.Flags | |
* @param modifierRange - a two element array describing the location of | |
* modifiers for this member within its source range. The first integer | |
* is the first character of the first modifier for this member, and | |
* the second integer is the last whitespace character preceeding the | |
* next part of this member declaration. If there are no modifiers present | |
* in this node's source code (i.e. default protection), this array | |
* contains two -1's. | |
* @param isConstructor - true if the method is a contructor, otherwise false | |
* @param returnType - the normalized return type of this method | |
* @param returnTypeRange - a two element array describing the location of the | |
* return type within the method's source range. The first integer is is | |
* the position of the first character in the return type, and the second | |
* integer is the position of the last character in the return type. | |
* For constructors, the contents of this array are -1's. | |
* If the return type of this method is qualified with '[]' following the | |
* parameter list, this array has four entries. In this case, the last | |
* two entries of the array are the inclusive source range of the array | |
* qualifiers. | |
* @param parameterTypes - an array of parameter types in the method declaration | |
* or <code>null</code> if the method has no parameters | |
* @param parameterNames - an array of parameter names in the method declaration | |
* or <code>null</code> if the method has no parameters | |
* @param parameterRange - a two element array describing the location of the | |
* parameter list in the method. The first integer is the location of the | |
* open parenthesis and the second integer is the location of the closing | |
* parenthesis. | |
* @param exceptions - an array of the names of exceptions thrown by this method | |
* or <code>null</code> if the method throws no exceptions | |
* @param exceptionRange - a two element array describing the location of the | |
* exception list in the method declaration. The first integer is the position | |
* of the first character in the first exception the method throws, and the | |
* second integer is the position of the last character of the last exception | |
* this method throws. | |
* @param bodyRange - a two element array describing the location of the method's body. | |
* The first integer is the first character following the method's | |
* parameter list, or exception list (if present). The second integer is the location | |
* of the last character in the method's source range. | |
*/ | |
DOMMethod(char[] document, int[] sourceRange, String name, int[] nameRange, int[] commentRange, int flags, int[] modifierRange, boolean isConstructor, String returnType, int[] returnTypeRange, String[] parameterTypes, String[] parameterNames, int[] parameterRange, String[] exceptions, int[] exceptionRange, int[] bodyRange) { | |
super(document, sourceRange, name, nameRange, commentRange, flags, modifierRange); | |
setMask(MASK_IS_CONSTRUCTOR, isConstructor); | |
fReturnType= returnType; | |
fReturnTypeRange= returnTypeRange; | |
fParameterTypes= parameterTypes; | |
fParameterNames= parameterNames; | |
fParameterRange= parameterRange; | |
fExceptionRange= exceptionRange; | |
fExceptions= exceptions; | |
setHasBody(true); | |
fBodyRange= bodyRange; | |
setMask(MASK_DETAILED_SOURCE_INDEXES, true); | |
} | |
/** | |
* Creates a new simple METHOD document fragment on the given range of the document. | |
* | |
* @param document - the document containing this node's original contents | |
* @param sourceRange - a two element array of integers describing the | |
* entire inclusive source range of this node within its document. | |
* Contents start on and include the character at the first position. | |
* Contents end on and include the character at the last position. | |
* An array of -1's indicates this node's contents do not exist | |
* in the document. | |
* @param name - the identifier portion of the name of this node, or | |
* <code>null</code> if this node does not have a name | |
* @param nameRange - a two element array of integers describing the | |
* entire inclusive source range of this node's name within its document, | |
* including any array qualifiers that might immediately follow the name | |
* or -1's if this node does not have a name. | |
* @param flags - an integer representing the modifiers for this member. The | |
* integer can be analyzed with org.eclipse.jdt.core.Flags | |
* @param isConstructor - true if the method is a contructor, otherwise false | |
* @param returnType - the normalized return type of this method | |
* @param parameterTypes - an array of parameter types in the method declaration | |
* or <code>null</code> if the method has no parameters | |
* @param parameterNames - an array of parameter names in the method declaration | |
* or <code>null</code> if the method has no parameters | |
* @param exceptions - an array of the names of exceptions thrown by this method | |
* or <code>null</code> if the method throws no exceptions | |
*/ | |
DOMMethod(char[] document, int[] sourceRange, String name, int[] nameRange, int flags, boolean isConstructor, String returnType, String[] parameterTypes, String[] parameterNames, String[] exceptions) { | |
this(document, sourceRange, name, nameRange, new int[] {-1, -1}, flags, new int[] {-1, -1}, isConstructor, returnType, new int[] {-1, -1}, parameterTypes, parameterNames, new int[] {-1, -1}, exceptions, new int[] {-1, -1}, new int[] {-1, -1}); | |
setMask(MASK_DETAILED_SOURCE_INDEXES, false); | |
} | |
/** | |
* @see IDOMMethod#addException(String) | |
*/ | |
public void addException(String name) throws IllegalArgumentException { | |
if (name == null) { | |
throw new IllegalArgumentException(Util.bind("dom.nullExceptionType"/*nonNLS*/)); | |
} | |
if (fExceptions == null) { | |
fExceptions= new String[1]; | |
fExceptions[0]= name; | |
} else { | |
fExceptions= appendString(fExceptions, name); | |
} | |
setExceptions(fExceptions); | |
} | |
/** | |
* @see IDOMMethod#addParameter(String, String) | |
*/ | |
public void addParameter(String type, String name) throws IllegalArgumentException { | |
if (type == null) { | |
throw new IllegalArgumentException(Util.bind("dom.nullTypeParameter"/*nonNLS*/)); | |
} | |
if (name == null) { | |
throw new IllegalArgumentException(Util.bind("dom.nullNameParameter"/*nonNLS*/)); | |
} | |
if (fParameterNames == null) { | |
fParameterNames= new String[1]; | |
fParameterNames[0]= name; | |
} else { | |
fParameterNames= appendString(fParameterNames, name); | |
} | |
if (fParameterTypes == null) { | |
fParameterTypes= new String[1]; | |
fParameterTypes[0]= type; | |
} else { | |
fParameterTypes= appendString(fParameterTypes, type); | |
} | |
setParameters(fParameterTypes, fParameterNames); | |
} | |
/** | |
* @see DOMMember#appendMemberBodyContents(CharArrayBuffer) | |
*/ | |
protected void appendMemberBodyContents(CharArrayBuffer buffer) { | |
if (fBody != null) { | |
buffer.append(fBody); | |
} else { | |
buffer.append(fDocument, fBodyRange[0], fBodyRange[1] + 1 - fBodyRange[0]); | |
} | |
} | |
/** | |
* @see DOMMember#appendMemberDeclarationContents(CharArrayBuffer) | |
*/ | |
protected void appendMemberDeclarationContents(CharArrayBuffer buffer) { | |
if (isConstructor()) { | |
buffer | |
.append(getConstructorName()) | |
.append(fDocument, fNameRange[1] + 1, fParameterRange[0] - fNameRange[1] - 1); | |
} else { | |
buffer.append(getReturnTypeContents()); | |
if (fReturnTypeRange[0] >= 0) { | |
buffer.append(fDocument, fReturnTypeRange[1] + 1, fNameRange[0] - fReturnTypeRange[1] - 1); | |
} else { | |
buffer.append(' '); | |
} | |
buffer | |
.append(getNameContents()) | |
.append(fDocument, fNameRange[1] + 1, fParameterRange[0] - fNameRange[1] - 1); | |
} | |
if (fParameterList != null) { | |
buffer.append(fParameterList); | |
} else { | |
buffer.append(fDocument, fParameterRange[0], fParameterRange[1] + 1 - fParameterRange[0]); | |
} | |
int start; | |
if (hasTrailingArrayQualifier() && isReturnTypeAltered()) { | |
start= fReturnTypeRange[3] + 1; | |
} else { | |
start= fParameterRange[1] + 1; | |
} | |
if (fExceptions != null) { | |
// add 'throws' keyword | |
if (fExceptionRange[0] >= 0) { | |
buffer.append(fDocument, start, fExceptionRange[0] - start); | |
} else { | |
buffer.append(" throws "/*nonNLS*/); | |
} | |
// add exception list | |
if (fExceptionList != null) { | |
buffer.append(fExceptionList); | |
// add space before body | |
if (fExceptionRange[0] >= 0) { | |
buffer.append(fDocument, fExceptionRange[1] + 1, fBodyRange[0] - fExceptionRange[1] - 1); | |
} else { | |
buffer.append(fDocument, fParameterRange[1] + 1, fBodyRange[0] - fParameterRange[1] - 1); | |
} | |
} else { | |
// add list and space before body | |
buffer.append(fDocument, fExceptionRange[0], fBodyRange[0] - fExceptionRange[0]); | |
} | |
} else { | |
// add space before body | |
if (fExceptionRange[0] >= 0) { | |
buffer.append(fDocument, fExceptionRange[1] + 1, fBodyRange[0] - fExceptionRange[1] - 1); | |
} else { | |
buffer.append(fDocument, start, fBodyRange[0] - start); | |
} | |
} | |
} | |
/** | |
* @see DOMNode#appendSimpleContents(CharArrayBuffer) | |
*/ | |
protected void appendSimpleContents(CharArrayBuffer buffer) { | |
// append eveything before my name | |
buffer.append(fDocument, fSourceRange[0], fNameRange[0] - fSourceRange[0]); | |
// append my name | |
if (isConstructor()) { | |
buffer.append(getConstructorName()); | |
} else { | |
buffer.append(fName); | |
} | |
// append everything after my name | |
buffer.append(fDocument, fNameRange[1] + 1, fSourceRange[1] - fNameRange[1]); | |
} | |
/** | |
* @see IDOMMethod#getBody() | |
*/ | |
public String getBody() { | |
becomeDetailed(); | |
if (hasBody()) { | |
if (fBody != null) { | |
return fBody; | |
} else { | |
return CharArrayOps.substring(fDocument, fBodyRange[0], fBodyRange[1] + 1 - fBodyRange[0]); | |
} | |
} else { | |
return null; | |
} | |
} | |
/** | |
* Returns the simple name of the enclsoing type for this constructor. | |
* If the constuctor is not currently enclosed in a type, the original | |
* name of the constructor as found in the documnent is returned. | |
*/ | |
protected String getConstructorName() { | |
if (isConstructor()) { | |
if (getParent() != null) { | |
return getParent().getName(); | |
} else { | |
// If there is no parent use the original name | |
return new String(getNameContents()); | |
} | |
} else { | |
return null; | |
} | |
} | |
/** | |
* @see DOMNode#getDetailedNode() | |
*/ | |
protected DOMNode getDetailedNode() { | |
return (DOMNode)getFactory().createMethod(getContents()); | |
} | |
/** | |
* @see IDOMMethod#getExceptions() | |
*/ | |
public String[] getExceptions() { | |
return fExceptions; | |
} | |
/** | |
* @see IDOMNode#getJavaElement | |
*/ | |
public IJavaElement getJavaElement(IJavaElement parent) throws IllegalArgumentException { | |
if (parent.getElementType() == IJavaElement.TYPE) { | |
// translate parameter types to signatures | |
String[] sigs= null; | |
if (fParameterTypes != null) { | |
sigs= new String[fParameterTypes.length]; | |
int i; | |
for (i= 0; i < fParameterTypes.length; i++) { | |
sigs[i]= Signature.createTypeSignature(fParameterTypes[i].toCharArray(), false); | |
} | |
} | |
String name= null; | |
if (isConstructor()) { | |
name= getConstructorName(); | |
} else { | |
name= getName(); | |
} | |
return ((IType)parent).getMethod(name, sigs); | |
} else { | |
throw new IllegalArgumentException(Util.bind("element.illegalParent"/*nonNLS*/)); | |
} | |
} | |
/** | |
* @see DOMMember#getMemberDeclarationStartPosition() | |
*/ | |
protected int getMemberDeclarationStartPosition() { | |
if (fReturnTypeRange[0] >= 0) { | |
return fReturnTypeRange[0]; | |
} else { | |
return fNameRange[0]; | |
} | |
} | |
/** | |
* @see IDOMNode#getName() | |
*/ | |
public String getName() { | |
if (isConstructor()) { | |
return null; | |
} else { | |
return super.getName(); | |
} | |
} | |
/** | |
* @see IDOMNode#getNodeType() | |
*/ | |
public int getNodeType() { | |
return IDOMNode.METHOD; | |
} | |
/** | |
* @see IDOMMethod#getParameterNames() | |
*/ | |
public String[] getParameterNames() { | |
return fParameterNames; | |
} | |
/** | |
* @see IDOMMethod#getParameterTypes() | |
*/ | |
public String[] getParameterTypes() { | |
return fParameterTypes; | |
} | |
/** | |
* @see IDOMMethod#getReturnType() | |
*/ | |
public String getReturnType() { | |
if (isConstructor()) { | |
return null; | |
} else { | |
return fReturnType; | |
} | |
} | |
/** | |
* Returns the source code to be used for this method's return type | |
*/ | |
protected char[] getReturnTypeContents() { | |
if (isConstructor()) { | |
return null; | |
} else { | |
if (isReturnTypeAltered()) { | |
return fReturnType.toCharArray(); | |
} else { | |
return CharArrayOps.subarray(fDocument, fReturnTypeRange[0], fReturnTypeRange[1] + 1 - fReturnTypeRange[0]); | |
} | |
} | |
} | |
/** | |
* Returns true if this method's return type has | |
* array qualifiers ('[]') following the parameter list. | |
*/ | |
protected boolean hasTrailingArrayQualifier() { | |
return fReturnTypeRange.length > 2; | |
} | |
/** | |
* @see IDOMMethod#isConstructor() | |
*/ | |
public boolean isConstructor() { | |
return getMask(MASK_IS_CONSTRUCTOR); | |
} | |
/** | |
* Returns true if this method's return type has been altered | |
* from the original document contents. | |
*/ | |
protected boolean isReturnTypeAltered() { | |
return getMask(MASK_RETURN_TYPE_ALTERED); | |
} | |
/** | |
* @see IDOMNode#isSigantureEqual(IDOMNode). | |
* | |
* <p>Two methods have equal signatures if there names are the same | |
* and their parameter types are the same. | |
*/ | |
public boolean isSignatureEqual(IDOMNode node) { | |
boolean ok= node.getNodeType() == getNodeType(); | |
if (ok) { | |
IDOMMethod method= (IDOMMethod)node; | |
ok = (isConstructor() && method.isConstructor()) || | |
(!isConstructor() && !method.isConstructor()); | |
if (ok && !isConstructor()) { | |
ok= getName().equals(method.getName()); | |
} | |
if (!ok) { | |
return false; | |
} | |
String[] types= method.getParameterTypes(); | |
if (fParameterTypes == null || fParameterTypes.length == 0) { | |
// this method has no parameters | |
if (types == null || types.length == 0) { | |
// the other method has no parameters either | |
return true; | |
} | |
} else { | |
// this method has parameters | |
if (types == null || types.length == 0) { | |
// the other method has no parameters | |
return false; | |
} | |
if (fParameterTypes.length != types.length) { | |
// the methods have a different number of parameters | |
return false; | |
} | |
int i; | |
for (i= 0; i < types.length; i++) { | |
if (!fParameterTypes[i].equals(types[i])) { | |
return false; | |
} | |
} | |
return true; | |
} | |
} | |
return false; | |
} | |
/** | |
* @see DOMNode | |
*/ | |
protected DOMNode newDOMNode() { | |
return new DOMMethod(); | |
} | |
/** | |
* Offsets all the source indexes in this node by the given amount. | |
*/ | |
protected void offset(int offset) { | |
super.offset(offset); | |
offsetRange(fBodyRange, offset); | |
offsetRange(fExceptionRange, offset); | |
offsetRange(fParameterRange, offset); | |
offsetRange(fReturnTypeRange, offset); | |
} | |
/** | |
* @see IDOMMethod#setBody | |
*/ | |
public void setBody(String body) { | |
becomeDetailed(); | |
fragment(); | |
fBody= body; | |
setHasBody(body != null); | |
if (!hasBody()) { | |
fBody= ";"/*nonNLS*/+JavaModelManager.LINE_SEPARATOR; | |
} | |
} | |
/** | |
* Sets the end of the body range | |
*/ | |
void setBodyRangeEnd(int end) { | |
fBodyRange[1] = end; | |
} | |
/** | |
* @see IDOMMethod#setConstructor(boolean) | |
*/ | |
public void setConstructor(boolean b) { | |
becomeDetailed(); | |
setMask(MASK_IS_CONSTRUCTOR, b); | |
fragment(); | |
} | |
/** | |
* @see IDOMMethod#setExceptions(char[][]) | |
*/ | |
public void setExceptions(String[] names) { | |
becomeDetailed(); | |
if (names == null || names.length == 0) { | |
fExceptions= null; | |
} else { | |
fExceptions= names; | |
CharArrayBuffer buffer = new CharArrayBuffer(); | |
char[] comma = new char[] {',', ' '}; | |
for (int i = 0, length = names.length; i < length; i++) { | |
if (i > 0) | |
buffer.append(comma); | |
buffer.append(names[i]); | |
} | |
fExceptionList= buffer.getContents(); | |
} | |
fragment(); | |
} | |
/** | |
* @see IDOMMethod#setName | |
*/ | |
public void setName(String name) { | |
if (name == null) { | |
throw new IllegalArgumentException(Util.bind("element.nullName"/*nonNLS*/)); | |
} else { | |
super.setName(name); | |
} | |
} | |
/** | |
* @see IDOMMethod#setParameters(char[][], char[][]) | |
*/ | |
public void setParameters(String[] types, String[] names) throws IllegalArgumentException { | |
becomeDetailed(); | |
if (types== null || names == null) { | |
if (types == null && names == null) { | |
fParameterTypes= null; | |
fParameterNames= null; | |
fParameterList= new char[] {'(',')'}; | |
} else { | |
throw new IllegalArgumentException(Util.bind("dom.mismatchArgNamesAndTypes"/*nonNLS*/)); | |
} | |
} else if (names.length != types.length) { | |
throw new IllegalArgumentException(Util.bind("dom.mismatchArgNamesAndTypes"/*nonNLS*/)); | |
} else if (names.length == 0) { | |
setParameters(null, null); | |
} else { | |
fParameterNames= names; | |
fParameterTypes= types; | |
CharArrayBuffer parametersBuffer = new CharArrayBuffer(); | |
parametersBuffer.append("("/*nonNLS*/); | |
char[] comma = new char[] {',', ' '}; | |
for (int i = 0; i < names.length; i++) { | |
if (i > 0) { | |
parametersBuffer.append(comma); | |
} | |
parametersBuffer | |
.append(types[i]) | |
.append(' ') | |
.append(names[i]); | |
} | |
parametersBuffer.append(')'); | |
fParameterList= parametersBuffer.getContents(); | |
} | |
fragment(); | |
} | |
/** | |
* @see IDOMMethod#setReturnType(char[]) | |
*/ | |
public void setReturnType(String name) throws IllegalArgumentException { | |
if (name == null) { | |
throw new IllegalArgumentException(Util.bind("dom.nullReturnType"/*nonNLS*/)); | |
} | |
becomeDetailed(); | |
fragment(); | |
setReturnTypeAltered(true); | |
fReturnType= name; | |
} | |
/** | |
* Sets the state of this method declaration as having | |
* the return type altered from the original document. | |
*/ | |
protected void setReturnTypeAltered(boolean typeAltered) { | |
setMask(MASK_RETURN_TYPE_ALTERED, typeAltered); | |
} | |
/** | |
*/ | |
protected void setSourceRangeEnd(int end) { | |
super.setSourceRangeEnd(end); | |
fBodyRange[1]= end; | |
} | |
/** | |
* @see DOMNode#shareContents(DOMNode) | |
*/ | |
protected void shareContents(DOMNode node) { | |
super.shareContents(node); | |
DOMMethod method= (DOMMethod)node; | |
fBody= method.fBody; | |
fBodyRange= rangeCopy(method.fBodyRange); | |
fExceptionList= method.fExceptionList; | |
fExceptionRange= rangeCopy(method.fExceptionRange); | |
fExceptions= method.fExceptions; | |
fParameterList= method.fParameterList; | |
fParameterNames= method.fParameterNames; | |
fParameterRange= rangeCopy(method.fParameterRange); | |
fParameterTypes= method.fParameterTypes; | |
fReturnType= method.fReturnType; | |
fReturnTypeRange= rangeCopy(method.fReturnTypeRange); | |
} | |
/** | |
* @see IDOMNode#toString() | |
*/ | |
public String toString() { | |
if (isConstructor()) { | |
return "CONSTRUCTOR"/*nonNLS*/; | |
} else { | |
return "METHOD: "/*nonNLS*/ + getName(); | |
} | |
} | |
} |