@@ -29,14 +29,20 @@ import org.eclipse.jdt.internal.compiler.lookup.*; | |||
import org.eclipse.jdt.internal.compiler.parser.Parser; | |||
import org.eclipse.jdt.internal.core.util.CharArrayOps; | |||
public class AdviceDeclaration extends MethodDeclaration implements IAjDeclaration { | |||
/** | |||
* Represents before, after and around advice in an aspect. | |||
* Will generate a method corresponding to the body of the advice with an | |||
* attribute including additional information. | |||
* | |||
* @author Jim Hugunin | |||
*/ | |||
public class AdviceDeclaration extends MethodDeclaration { | |||
public PointcutDesignator pointcutDesignator; | |||
int baseArgumentCount; | |||
public Argument extraArgument; | |||
public AdviceKind kind; // adviceMunger; | |||
public AdviceKind kind; | |||
private int extraArgumentFlags = 0; | |||
@@ -154,21 +160,6 @@ public class AdviceDeclaration extends MethodDeclaration implements IAjDeclarati | |||
public void generateCode(ClassScope classScope, ClassFile classFile) { | |||
if (proceedMethodBinding != null) { | |||
// MethodDeclaration dec = | |||
// AstUtil.makeMethodDeclaration(proceedMethodBinding); | |||
// | |||
// List stmts = new ArrayList(); | |||
// | |||
// Expression expr = AstUtil.makeLocalVariableReference(dec.arguments[0].binding); | |||
// | |||
// | |||
// stmts.add(AstUtil.makeReturnStatement(expr)); | |||
// | |||
// AstUtil.setStatements(dec, stmts); | |||
// dec.scope = this.scope; | |||
// dec.generateCode(classScope, classFile); | |||
} | |||
super.generateCode(classScope, classFile); | |||
if (proceedMethodBinding != null) { | |||
generateProceedMethod(classScope, classFile); | |||
@@ -192,12 +183,9 @@ public class AdviceDeclaration extends MethodDeclaration implements IAjDeclarati | |||
public void finishParsing() { | |||
//kind = AdviceKind.stringToKind(new String(selector)); | |||
//System.out.println(this + " kind " + kind + " name " + new String(selector)); | |||
//selector = ("ajc_" + kind.toString() + "_" + Integer.toHexString(position)).toCharArray(); | |||
//System.out.println(this + " kind " + kind + " name " + new String(selector)); | |||
public void postParse(TypeDeclaration typeDec) { | |||
this.selector = | |||
NameMangler.adviceName(EclipseWorld.fromBinding(typeDec.binding), kind, sourceStart).toCharArray(); | |||
if (arguments != null) { | |||
baseArgumentCount = arguments.length; | |||
} | |||
@@ -222,15 +210,11 @@ public class AdviceDeclaration extends MethodDeclaration implements IAjDeclarati | |||
arguments[index++] = makeFinalArgument("thisJoinPointStaticPart", AjTypeConstants.getJoinPointStaticPartType()); | |||
arguments[index++] = makeFinalArgument("thisJoinPoint", AjTypeConstants.getJoinPointType()); | |||
arguments[index++] = makeFinalArgument("thisEnclosingJoinPointStaticPart", AjTypeConstants.getJoinPointStaticPartType()); | |||
//modifiers = checkAndSetModifiers(modifiers); | |||
if (pointcutDesignator.isError()) { | |||
//System.err.println("ignoring further investigation for: " + this); | |||
this.ignoreFurtherInvestigation = true; | |||
} | |||
pointcutDesignator.postParse(typeDec, this); | |||
} | |||
private int checkAndSetModifiers(int modifiers, ClassScope scope) { | |||
@@ -317,34 +301,4 @@ public class AdviceDeclaration extends MethodDeclaration implements IAjDeclarati | |||
s += toStringStatements(tab + 1); | |||
return s; | |||
} | |||
public void modifyKind(char[] name) { | |||
this.selector = CharArrayOps.concat(selector, name); | |||
} | |||
// public boolean finishResolveTypes(SourceTypeBinding sourceTypeBinding) { | |||
// if (this.ignoreFurtherInvestigation) { | |||
// return false; | |||
// } | |||
// | |||
// if ((binding.modifiers & AccUnresolved) == 0) return true; | |||
// | |||
// //System.err.println("this " + this + ", " + binding.modifiers); | |||
// binding.modifiers ^= AccUnresolved; | |||
// //System.err.println(" post " + binding.modifiers); | |||
// | |||
// | |||
// return super.finishResolveTypes(sourceTypeBinding) && | |||
// pointcutDesignator.finishResolveTypes(this, this.binding, baseArgumentCount, sourceTypeBinding) | |||
// ; | |||
// } | |||
public void postParse(TypeDeclaration typeDec) { | |||
this.selector = | |||
NameMangler.adviceName(EclipseWorld.fromBinding(typeDec.binding), kind, sourceStart).toCharArray(); | |||
finishParsing(); | |||
pointcutDesignator.postParse(typeDec, this); | |||
} | |||
} |
@@ -58,6 +58,19 @@ public class AspectDeclaration extends MemberTypeDeclaration { | |||
return (modifiers & AccAbstract) != 0; | |||
} | |||
public void resolve() { | |||
if (binding == null || ignoreFurtherInvestigation) { | |||
ignoreFurtherInvestigation = true; | |||
return; | |||
} | |||
if (typeX != null) typeX.checkPointcutDeclarations(); | |||
super.resolve(); | |||
} | |||
public void checkSpec(ClassScope scope) { | |||
if (ignoreFurtherInvestigation) return; | |||
if (dominatesPattern != null) { | |||
@@ -132,18 +145,6 @@ public class AspectDeclaration extends MemberTypeDeclaration { | |||
return; | |||
} | |||
} | |||
//XXX need to move this somewhere that it will be applied to classes and interfaces | |||
// as well as to aspects, also need to handle inheritance and overriding | |||
// ResolvedMember[] pointcuts = myType.getDeclaredPointcuts(); | |||
// for (int i=0, len=pointcuts.length; i < len; i++) { | |||
// for (int j=i+1; j < len; j++) { | |||
// if (pointcuts[i].getName().equals(pointcuts[j].getName())) { | |||
// scope.problemReporter().signalError(0, 0, | |||
// "duplicate pointcut name: " + pointcuts[i].getName()); | |||
// } | |||
// } | |||
// } | |||
} | |||
@@ -26,7 +26,7 @@ import org.eclipse.jdt.internal.compiler.lookup.*; | |||
import org.eclipse.jdt.internal.compiler.lookup.ClassScope; | |||
import org.eclipse.jdt.internal.compiler.parser.Parser; | |||
public class DeclareDeclaration extends MethodDeclaration implements IAjDeclaration { | |||
public class DeclareDeclaration extends MethodDeclaration { | |||
public Declare declare; | |||
/** |
@@ -1,22 +0,0 @@ | |||
/* ******************************************************************* | |||
* Copyright (c) 2002 Palo Alto Research Center, Incorporated (PARC). | |||
* All rights reserved. | |||
* This program and the accompanying materials are made available | |||
* under the terms of the Common Public License v1.0 | |||
* which accompanies this distribution and is available at | |||
* http://www.eclipse.org/legal/cpl-v10.html | |||
* | |||
* Contributors: | |||
* Xerox/PARC initial implementation | |||
* ******************************************************************/ | |||
package org.aspectj.ajdt.internal.compiler.ast; | |||
import org.eclipse.jdt.internal.compiler.ast.TypeDeclaration; | |||
public interface IAjDeclaration { | |||
// public String toString(int tab); | |||
// void postParse(TypeDeclaration typeDec); | |||
} |
@@ -24,7 +24,7 @@ import org.eclipse.jdt.internal.compiler.ast.*; | |||
import org.eclipse.jdt.internal.compiler.lookup.*; | |||
import org.eclipse.jdt.internal.compiler.util.CharOperation; | |||
public abstract class InterTypeDeclaration extends MethodDeclaration implements IAjDeclaration { | |||
public abstract class InterTypeDeclaration extends MethodDeclaration { | |||
//public AstNode myDeclaration; | |||
public TypeReference onType; | |||
protected ReferenceBinding onTypeBinding; |
@@ -17,6 +17,7 @@ import java.io.*; | |||
import java.io.IOException; | |||
import org.aspectj.ajdt.internal.compiler.lookup.EclipseWorld; | |||
import org.aspectj.ajdt.internal.core.builder.EclipseSourceContext; | |||
import org.aspectj.weaver.*; | |||
import org.aspectj.weaver.ResolvedPointcutDefinition; | |||
import org.aspectj.weaver.patterns.Pointcut; | |||
@@ -25,39 +26,28 @@ import org.eclipse.jdt.internal.compiler.ast.*; | |||
import org.eclipse.jdt.internal.compiler.lookup.*; | |||
import org.eclipse.jdt.internal.compiler.lookup.ClassScope; | |||
import org.eclipse.jdt.internal.compiler.parser.Parser; | |||
public class PointcutDeclaration extends MethodDeclaration implements IAjDeclaration { | |||
import org.eclipse.jdt.internal.compiler.util.CharOperation; | |||
/** | |||
* pointcut [declaredModifiers] [declaredName]([arguments]): [pointcutDesignator]; | |||
* | |||
* <p>No method will actually be generated for this node but an attribute | |||
* will be added to the enclosing class.</p> | |||
* | |||
* @author Jim Hugunin | |||
*/ | |||
public class PointcutDeclaration extends MethodDeclaration { | |||
public static final char[] mangledPrefix = "ajc$pointcut$".toCharArray(); | |||
public PointcutDesignator pointcutDesignator; | |||
private int declaredModifiers; | |||
private String declaredName; | |||
public PointcutDeclaration(CompilationResult compilationResult) { | |||
super(compilationResult); | |||
this.returnType = TypeReference.baseTypeReference(T_void, 0); | |||
} | |||
// public PointcutDeclaration(MethodDeclaration decl, Parser parser) { | |||
// this(decl.compilationResult); | |||
// this.sourceEnd = decl.sourceEnd; | |||
// this.sourceStart = decl.sourceStart; | |||
// | |||
// this.arguments = decl.arguments; | |||
// if (this.arguments == null) this.arguments = new Argument[0]; | |||
// this.modifiers = decl.modifiers; | |||
//// if ((modifiers & AccAbstract) == 0) { | |||
//// modifiers |= AccNative; | |||
//// } | |||
//// modifiers |= AccSemicolonBody; //XXX hack to make me have no body | |||
// | |||
// this.modifiersSourceStart = decl.modifiersSourceStart; | |||
// this.selector = decl.selector; | |||
// if (decl.thrownExceptions != null && decl.thrownExceptions.length > 0) { | |||
// //XXX need a better problem to report | |||
// TypeReference e1 = decl.thrownExceptions[0]; | |||
// parser.problemReporter().parseError(e1.sourceStart, e1.sourceEnd, | |||
// new char[0], "throws", new String[] {":"}); | |||
// } | |||
// } | |||
private Pointcut getPointcut() { | |||
if (pointcutDesignator == null) { | |||
return Pointcut.makeMatchesNothing(Pointcut.RESOLVED); | |||
@@ -66,26 +56,54 @@ public class PointcutDeclaration extends MethodDeclaration implements IAjDeclara | |||
} | |||
} | |||
public void parseStatements( | |||
Parser parser, | |||
CompilationUnitDeclaration unit) { | |||
// do nothing | |||
} | |||
public void postParse(TypeDeclaration typeDec) { | |||
if (arguments == null) arguments = new Argument[0]; | |||
this.declaredModifiers = modifiers; | |||
this.declaredName = new String(selector); | |||
selector = CharOperation.concat(mangledPrefix, '$', selector, '$', | |||
Integer.toHexString(sourceStart).toCharArray()); | |||
if (pointcutDesignator == null) return; //XXX | |||
pointcutDesignator.postParse(typeDec, this); | |||
} | |||
public void resolveStatements(ClassScope upperScope) { | |||
if (isAbstract()) this.modifiers |= AccSemicolonBody; | |||
if (binding == null || ignoreFurtherInvestigation) return; | |||
if (pointcutDesignator != null) { | |||
pointcutDesignator.finishResolveTypes(this, this.binding, arguments.length, | |||
upperScope.referenceContext.binding); | |||
} | |||
super.resolveStatements(upperScope); | |||
} | |||
public ResolvedPointcutDefinition makeResolvedPointcutDefinition() { | |||
//System.out.println("pc: " + getPointcut()); | |||
return new ResolvedPointcutDefinition( | |||
ResolvedPointcutDefinition ret = new ResolvedPointcutDefinition( | |||
EclipseWorld.fromBinding(this.binding.declaringClass), | |||
this.modifiers, // & ~AccNative, | |||
new String(selector), | |||
declaredModifiers, | |||
declaredName, | |||
EclipseWorld.fromBindings(this.binding.parameters), | |||
getPointcut()); | |||
ret.setPosition(sourceStart, sourceEnd); | |||
ret.setSourceContext(new EclipseSourceContext(compilationResult)); | |||
return ret; | |||
} | |||
public AjAttribute makeAttribute() { | |||
return new AjAttribute.PointcutDeclarationAttribute(makeResolvedPointcutDefinition()); | |||
// return new Attribute() { | |||
// public char[] getAttributeName() { return ResolvedPointcutDefinition.AttributeName.toCharArray(); } | |||
// public void writeTo(DataOutputStream s) throws IOException { | |||
// makeResolvedPointcut().writeAttribute(s); | |||
// } | |||
// }; | |||
} | |||
/** | |||
@@ -99,28 +117,13 @@ public class PointcutDeclaration extends MethodDeclaration implements IAjDeclara | |||
return; | |||
} | |||
// public boolean finishResolveTypes(SourceTypeBinding sourceTypeBinding) { | |||
// if (!super.finishResolveTypes(sourceTypeBinding)) return false; | |||
// if (pointcutDesignator != null) { | |||
// return pointcutDesignator.finishResolveTypes(this, this.binding, arguments.length, sourceTypeBinding); | |||
// } else { | |||
// return true; | |||
// } | |||
// } | |||
public String toString(int tab) { | |||
StringBuffer buf = new StringBuffer(); | |||
buf.append(tabString(tab)); | |||
if (modifiers != 0) { | |||
buf.append(modifiersString(modifiers)); | |||
} | |||
// if (modifiers != AccNative) { | |||
// buf.append(modifiersString(modifiers & ~AccNative)); | |||
// } | |||
buf.append("pointcut "); | |||
buf.append(new String(selector)); | |||
buf.append("("); | |||
@@ -137,35 +140,4 @@ public class PointcutDeclaration extends MethodDeclaration implements IAjDeclara | |||
buf.append(";"); | |||
return buf.toString(); | |||
} | |||
public void parseStatements( | |||
Parser parser, | |||
CompilationUnitDeclaration unit) { | |||
if (pointcutDesignator == null) { | |||
//XXXthrow new RuntimeException("unimplemented"); | |||
} else { | |||
// do nothing | |||
} | |||
} | |||
public void postParse(TypeDeclaration typeDec) { | |||
if (arguments == null) arguments = new Argument[0]; | |||
if (pointcutDesignator == null) return; //XXX | |||
pointcutDesignator.postParse(typeDec, this); | |||
} | |||
public void resolveStatements(ClassScope upperScope) { | |||
if (isAbstract()) this.modifiers |= AccSemicolonBody; | |||
if (binding == null || ignoreFurtherInvestigation) return; | |||
if (pointcutDesignator != null) { | |||
pointcutDesignator.finishResolveTypes(this, this.binding, arguments.length, | |||
upperScope.referenceContext.binding); | |||
} | |||
super.resolveStatements(upperScope); | |||
} | |||
} |
@@ -17,6 +17,7 @@ import java.util.*; | |||
import org.aspectj.ajdt.internal.compiler.ast.*; | |||
import org.aspectj.ajdt.internal.compiler.ast.PointcutDeclaration; | |||
import org.aspectj.bridge.MessageUtil; | |||
import org.aspectj.weaver.*; | |||
import org.eclipse.jdt.internal.compiler.ast.AbstractMethodDeclaration; | |||
import org.eclipse.jdt.internal.compiler.lookup.*; | |||
@@ -82,7 +83,8 @@ public class EclipseObjectType extends ResolvedTypeX.Name { | |||
if (amd == null) continue; //??? | |||
if (amd instanceof PointcutDeclaration) { | |||
PointcutDeclaration d = (PointcutDeclaration)amd; | |||
declaredPointcuts.add(d.makeResolvedPointcutDefinition()); | |||
ResolvedPointcutDefinition df = d.makeResolvedPointcutDefinition(); | |||
declaredPointcuts.add(df); | |||
} else { | |||
//XXX this doesn't handle advice quite right | |||
declaredMethods.add(eclipseWorld().makeResolvedMember(m)); | |||
@@ -120,4 +122,38 @@ public class EclipseObjectType extends ResolvedTypeX.Name { | |||
return crosscuttingMembers; | |||
} | |||
//XXX make sure this is applied to classes and interfaces | |||
public void checkPointcutDeclarations() { | |||
ResolvedMember[] pointcuts = getDeclaredPointcuts(); | |||
for (int i=0, len=pointcuts.length; i < len; i++) { | |||
if (pointcuts[i].isAbstract()) { | |||
if (!this.isAspect()) { | |||
MessageUtil.error( | |||
"abstract pointcut only allowed in aspect" + pointcuts[i].getName(), | |||
pointcuts[i].getSourceLocation()); | |||
} else if (!this.isAbstract()) { | |||
MessageUtil.error( | |||
"abstract pointcut in concrete aspect" + pointcuts[i].getName(), | |||
pointcuts[i].getSourceLocation()); | |||
} | |||
} | |||
for (int j=i+1; j < len; j++) { | |||
if (pointcuts[i].getName().equals(pointcuts[j].getName())) { | |||
eclipseWorld().getMessageHandler().handleMessage( | |||
MessageUtil.error( | |||
"duplicate pointcut name: " + pointcuts[j].getName(), | |||
pointcuts[j].getSourceLocation())); | |||
} | |||
} | |||
} | |||
//XXX now check all inherited pointcuts to be sure that they're handled reasonably | |||
} | |||
} |
@@ -15,12 +15,17 @@ package org.aspectj.ajdt.internal.compiler.lookup; | |||
import java.util.*; | |||
import org.aspectj.bridge.*; | |||
import org.aspectj.weaver.*; | |||
import org.aspectj.weaver.patterns.*; | |||
import org.aspectj.bridge.*; | |||
import org.eclipse.jdt.internal.compiler.lookup.*; | |||
import org.eclipse.jdt.internal.compiler.util.CharOperation; | |||
/** | |||
* Adaptor from org.eclipse.jdt.internal.compiler.lookup.Scope to org.aspectj.weaver.IScope | |||
* | |||
* @author Jim Hugunin | |||
*/ | |||
public class EclipseScope implements IScope { | |||
private Scope scope; | |||
private EclipseWorld world; |
@@ -16,6 +16,7 @@ | |||
import java.lang.reflect.Modifier; | |||
import java.util.Iterator; | |||
import org.aspectj.ajdt.internal.compiler.ast.*; | |||
import org.aspectj.ajdt.internal.compiler.ast.Proceed; | |||
import org.aspectj.ajdt.internal.compiler.lookup.EclipseWorld; | |||
import org.aspectj.util.FuzzyBoolean; | |||
@@ -29,7 +30,16 @@ import org.eclipse.jdt.internal.compiler.impl.CompilerOptions; | |||
import org.eclipse.jdt.internal.compiler.lookup.*; | |||
import org.eclipse.jdt.internal.compiler.lookup.TypeBinding; | |||
import org.eclipse.jdt.internal.compiler.problem.ProblemReporter; | |||
import org.eclipse.jdt.internal.compiler.util.CharOperation; | |||
/** | |||
* Extends problem reporter to support compiler-side implementation of declare soft. | |||
* Also overrides error reporting for the need to implement abstract methods to | |||
* account for inter-type declarations and pointcut declarations. This second | |||
* job might be better done directly in the SourceTypeBinding/ClassScope classes. | |||
* | |||
* @author Jim Hugunin | |||
*/ | |||
public class AjProblemReporter extends ProblemReporter { | |||
private static final boolean DUMP_STACK = false; | |||
@@ -87,33 +97,32 @@ public class AjProblemReporter extends ProblemReporter { | |||
super.unhandledException(exceptionType, location); | |||
} | |||
public void handle( | |||
int problemId, | |||
String[] problemArguments, | |||
int severity, | |||
int problemStartPosition, | |||
int problemEndPosition, | |||
ReferenceContext referenceContext, | |||
CompilationResult unitResult) { | |||
if (severity != Ignore && DUMP_STACK) { | |||
Thread.currentThread().dumpStack(); | |||
private boolean isPointcutDeclaration(MethodBinding binding) { | |||
return CharOperation.startsWith(binding.selector, PointcutDeclaration.mangledPrefix); | |||
} | |||
public void abstractMethodCannotBeOverridden( | |||
SourceTypeBinding type, | |||
MethodBinding concreteMethod) | |||
{ | |||
if (isPointcutDeclaration(concreteMethod)) { | |||
return; | |||
} | |||
super.handle( | |||
problemId, | |||
problemArguments, | |||
severity, | |||
problemStartPosition, | |||
problemEndPosition, | |||
referenceContext, | |||
unitResult); | |||
super.abstractMethodCannotBeOverridden(type, concreteMethod); | |||
} | |||
public void abstractMethodMustBeImplemented( | |||
SourceTypeBinding type, | |||
MethodBinding abstractMethod) | |||
{ | |||
// if this is a PointcutDeclaration then there is no error | |||
if (isPointcutDeclaration(abstractMethod)) { | |||
return; | |||
} | |||
// if we implemented this method by an inter-type declaration, then there is no error | |||
//??? be sure this is always right | |||
ResolvedTypeX onTypeX = world.fromEclipse(type); //abstractMethod.declaringClass); | |||
@@ -132,4 +141,28 @@ public class AjProblemReporter extends ProblemReporter { | |||
super.abstractMethodMustBeImplemented(type, abstractMethod); | |||
} | |||
public void handle( | |||
int problemId, | |||
String[] problemArguments, | |||
int severity, | |||
int problemStartPosition, | |||
int problemEndPosition, | |||
ReferenceContext referenceContext, | |||
CompilationResult unitResult) { | |||
if (severity != Ignore && DUMP_STACK) { | |||
Thread.currentThread().dumpStack(); | |||
} | |||
super.handle( | |||
problemId, | |||
problemArguments, | |||
severity, | |||
problemStartPosition, | |||
problemEndPosition, | |||
referenceContext, | |||
unitResult); | |||
} | |||
} |
@@ -14,6 +14,7 @@ | |||
package org.aspectj.weaver; | |||
import java.io.*; | |||
import java.lang.reflect.Modifier; | |||
import org.aspectj.bridge.ISourceLocation; | |||
@@ -152,5 +153,13 @@ public class ResolvedMember extends Member implements IHasPosition { | |||
this.end = sourceEnd; | |||
} | |||
public void setSourceContext(ISourceContext sourceContext) { | |||
this.sourceContext = sourceContext; | |||
} | |||
public boolean isAbstract() { | |||
return Modifier.isAbstract(modifiers); | |||
} | |||
} | |||
@@ -58,6 +58,13 @@ public class TypeX { | |||
public final int hashCode() { | |||
return signature.hashCode(); | |||
} | |||
public static TypeX makeArray(TypeX base, int dims) { | |||
StringBuffer sig = new StringBuffer(); | |||
for (int i=0; i < dims; i++) sig.append("["); | |||
sig.append(base.getSignature()); | |||
return TypeX.forSignature(sig.toString()); | |||
} | |||
/** | |||
* Constructs a TypeX for a java language type name. For example: |
@@ -15,6 +15,7 @@ package org.aspectj.weaver.patterns; | |||
import java.io.*; | |||
import org.aspectj.bridge.IMessage; | |||
import org.aspectj.util.FuzzyBoolean; | |||
import org.aspectj.weaver.*; | |||
import org.aspectj.weaver.ast.*; | |||
@@ -68,6 +69,10 @@ public class ArgsPointcut extends NameBindingPointcut { | |||
public void resolveBindings(IScope scope, Bindings bindings) { | |||
arguments.resolveBindings(scope, bindings, true); | |||
if (arguments.ellipsisCount > 1) { | |||
scope.message(IMessage.ERROR, this, | |||
"uses more than one .. in args (compiler limitation)"); | |||
} | |||
} | |||
public void postRead(ResolvedTypeX enclosingType) { |
@@ -324,6 +324,7 @@ public class WildTypePattern extends TypePattern { | |||
scope.getWorld().getLint().invalidAbsoluteTypeName.signal(cleanName, getSourceLocation()); | |||
} | |||
} else { | |||
if (dim != 0) type = TypeX.makeArray(type, dim); | |||
TypePattern ret = new ExactTypePattern(type, includeSubtypes); | |||
ret.copyLocationFrom(this); | |||
return ret; |