001: /*
002: * <copyright>
003: *
004: * Copyright 1997-2004 BBNT Solutions, LLC
005: * under sponsorship of the Defense Advanced Research Projects
006: * Agency (DARPA).
007: *
008: * You can redistribute this software and/or modify it under the
009: * terms of the Cougaar Open Source License as published on the
010: * Cougaar Open Source Website (www.cougaar.org).
011: *
012: * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
013: * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
014: * LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
015: * A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT
016: * OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
017: * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
018: * LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
019: * DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
020: * THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
021: * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
022: * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
023: *
024: * </copyright>
025: */
026:
027: package org.cougaar.lib.contract.lang.op.reflect;
028:
029: import java.util.*;
030: import java.lang.reflect.*;
031:
032: import org.cougaar.lib.contract.lang.*;
033: import org.cougaar.lib.contract.lang.op.OpCodes;
034:
035: /**
036: * "method" <code>Op</code>.
037: * <p>
038: * Behavior for overloaded methods may be incorrect(!).
039: **/
040:
041: public final class MethodOp extends OpImpl {
042:
043: public Method meth;
044: public Op[] argOps;
045: public Object[] argBuf;
046:
047: public static final Op[] zeroOps = {};
048: private static final Object[] zeroArgs = {};
049:
050: public MethodOp() {
051: }
052:
053: public final int getID() {
054: return OpCodes.METHOD_ID;
055: }
056:
057: /**
058: * <code>ReflectOp</code> should use <tt>parseMethod</tt>.
059: */
060: public final Op parse(final OpParser p) throws ParseException {
061: throw new ParseException(
062: "Internal use should be \"parseMethod\"");
063: }
064:
065: /**
066: * Parse the arguments for the given <code>Method</code>.
067: */
068: public final Op parseMethod(final Method meth, final OpParser p)
069: throws ParseException {
070: TypeList origTypeList = p.cloneTypeList();
071:
072: // gather some method info
073: Class[] params = meth.getParameterTypes();
074: int nparams = params.length;
075: Class retClass = meth.getReturnType();
076:
077: if (nparams == 0) {
078: // Assume that this is either (meth) or (apply meth u1),
079: // so we p.setTypeList to meth's retClass, expecting something like
080: // (FooClass:getBar BarClass:test)
081: p.setTypeList(retClass);
082: Op u1 = p.nextOp();
083: p.setTypeList(retClass);
084: if (u1 == null) {
085: // (meth[]) is (meth)
086: this .meth = meth;
087: this .argOps = zeroOps;
088: this .argBuf = zeroArgs;
089: return this ;
090: } else {
091: Op u2 = p.nextOp();
092: if (u2 != null) {
093: // (meth[] arg1 .. argN) is broken!
094: throw new ParseException("Method \""
095: + meth.getName() + "\" expecting "
096: + "zero arguments but given additional "
097: +
098: //u1.getClass().getName());
099: u1.getClass().getName() + " !!added: "
100: + u2.getClass().getName());
101:
102: }
103: // (meth[] u1) is (apply meth u1)
104: //
105: // This shorthand confuses both parser and user to no end...
106: //
107: int u1id = u1.getID();
108: if ((u1id == OpCodes.TRUE_ID)
109: || (u1id == OpCodes.FALSE_ID)) {
110: // (apply meth true) is (true)
111: // (apply meth false) is (false)
112: return u1;
113: } else {
114: // (apply meth u1)
115: // typical case
116: this .meth = meth;
117: this .argOps = zeroOps;
118: this .argBuf = zeroArgs;
119: ApplyOp aop = new ApplyOp(this , u1);
120: p.setTypeList(origTypeList);
121: return aop;
122: }
123: }
124: } else {
125: // Expecting (meth[nArgs] arg1 .. argN), where the args are each
126: // given the meth's type.
127: this .meth = meth;
128: this .argOps = new Op[nparams];
129: this .argBuf = new Object[nparams];
130: int i = 0;
131: final int maxi = (nparams - 1);
132: while (true) {
133: Op ui = p.nextOp();
134: if (ui == null) {
135: // (meth[nArgs] arg1 .. argN-k) is broken!
136: throw new ParseException("Method \""
137: + meth.getName() + "\" expecting "
138: + nparams + " argument"
139: + ((nparams > 1) ? "s" : "")
140: + " but given only " + i);
141: }
142: // check type
143: Class reti = ui.getReturnClass();
144: if (!(params[i].isAssignableFrom(reti))) {
145: throw new ParseException("Method \""
146: + meth.getName() + "\" argument " + i
147: + " expected to be of type " + params[i]
148: + " but given " + reti + " Op " + ui);
149: }
150: argOps[i] = ui;
151: // next param
152: i++;
153: // set arg type
154: if (i < maxi) {
155: // middle arg needs clone of type
156: p.setTypeList((TypeList) origTypeList.clone());
157: } else if (i == maxi) {
158: // last arg can take orig type
159: p.setTypeList(origTypeList);
160: } else {
161: // check for end of args
162: Op uN = p.nextOp();
163: if (uN != null) {
164: // (meth[nArgs] arg1 arg2 .. argN+k) is broken!
165: throw new ParseException("Method \""
166: + meth.getName() + "\" expecting "
167: + nparams + " argument"
168: + ((nparams > 1) ? "s" : "")
169: + " but given additional "
170: + uN.getClass().getName());
171: }
172: break;
173: }
174: }
175: // (meth[nArgs] arg1 .. argN)
176: p.setTypeList(retClass);
177: return this ;
178: }
179: }
180:
181: /**
182: * Parse the arguments for the ambiguous <code>Method</code>, which
183: * best matches an entry in the <code>List</code> of "possMeths".
184: * <p>
185: * More complex than <tt>parseMethod</tt>.
186: */
187: public final Op parseMethod(List possMeths, final OpParser p)
188: throws ParseException {
189: TypeList origTypeList = p.cloneTypeList();
190: int nPossMeths = possMeths.size();
191:
192: // get the range of parameter lengths
193: int minNParams = 0;
194: int maxNParams = 0;
195: for (int i = 0; i < nPossMeths; i++) {
196: Method mi = (Method) possMeths.get(i);
197: Class[] pi = mi.getParameterTypes();
198: int pin = pi.length;
199: if (i == 0) {
200: minNParams = pin;
201: maxNParams = pin;
202: } else if (pin < minNParams) {
203: minNParams = pin;
204: } else if (pin > maxNParams) {
205: maxNParams = pin;
206: }
207: }
208:
209: if (minNParams == 0) {
210: // maxNParams must be > 0, due to Java overloading rules
211: //
212: // potential confusion between "apply" method that takes zero
213: // arguments and methods which take parameters. Unable to
214: // take the nextOp, since the TypeList is unknown.
215: //
216: // Consider:
217: // "public String foo() {//1}"
218: // "public Boolean foo(String s) {//2};"
219: // "public String bar() {//3};"
220: // "public Boolean bar(String s) {//4};"
221: // (foo bar)
222: // is the "bar" used in an "apply":
223: // apply "String foo()" as the value passed to "Boolean bar(String)"
224: // bar(foo())
225: // or as a single "parameter":
226: // use "String bar()" as the parameter in "Boolean foo(String)"
227: // foo(bar())
228: //
229: // and it's worse when foo/bar are split between classes..
230: //
231: // solution: complain
232: //
233: StringBuffer sb = new StringBuffer();
234: sb.append("Method is ambiguous!\nPossible methods: {");
235: for (int i = 0; i < nPossMeths; i++) {
236: sb.append("\n ");
237: sb.append(ReflectOp.toString((Method) possMeths.get(i),
238: true));
239: }
240: sb.append("\n}");
241: throw new ParseException(sb.toString());
242: }
243:
244: // Expecting (meth[nArgs] arg1 .. argN), where the args are each
245: // given the meth's type and the number is unknown.
246: List lOps = new ArrayList(maxNParams);
247: while (true) {
248: Op ui = p.nextOp();
249: if (ui == null) {
250: break;
251: }
252: lOps.add(ui);
253: p.setTypeList((TypeList) origTypeList.clone());
254: }
255: int nlOps = lOps.size();
256: this .argOps = new Op[nlOps];
257: this .argBuf = new Object[nlOps];
258: for (int i = 0; i < nlOps; i++) {
259: argOps[i] = (Op) lOps.get(i);
260: }
261:
262: // pick the method
263: this .meth = pickClosestMethod(possMeths);
264:
265: // (meth[nArgs] arg1 .. argN)
266: p.setTypeList(meth.getReturnType());
267: return this ;
268: }
269:
270: /**
271: * Given a list of possible <code>Method</code>s and the <code>argOps</code>
272: * already parsed, pick the "closest" matching <code>Method</code> in
273: * the list.
274: * <p>
275: * This is fairly tricky -- Java does something clever here...
276: * <p>
277: * <pre>
278: * We'll do something OVERLY simple
279: * pick the one which matches exactly
280: * if none are exact, pick the single assignable method
281: * if multiple or none, complain
282: * </pre>
283: */
284: private final Method pickClosestMethod(final List possMeths)
285: throws ParseException {
286: int nargs = argOps.length;
287: int nmeths = possMeths.size();
288:
289: // assign ranks for each possible method
290: // -1 if any arg is not assignable
291: // +0 per exactly equal arg match
292: // +1 per assignable arg match not exactly equal
293: int[] rank = new int[nmeths];
294: int singleAssignableIdx = -1; // -1==unset, -2==multiple matches
295: for (int i = 0; i < nmeths; i++) {
296: Method mi = (Method) possMeths.get(i);
297: Class[] pi = mi.getParameterTypes();
298: if (pi.length == nargs) {
299: int ri = 0;
300: for (int j = 0;; j++) {
301: if (j >= nargs) {
302: if (ri == 0) {
303: // exact match. it doesn't matter if this matched
304: // an interface or implementation.
305: return mi;
306: }
307: if (singleAssignableIdx == -1) {
308: singleAssignableIdx = i; // unset --> set
309: } else {
310: singleAssignableIdx = -2; // set/multi --> multi
311: }
312: break;
313: }
314: Class pij = pi[j];
315: Class arj = argOps[j].getReturnClass();
316: // compare parameter to given argument
317: if (pij.equals(arj)) {
318: // ri += 0;
319: } else if (pij.isAssignableFrom(arj)) {
320: ri += 1;
321: } else {
322: // not assignable
323: ri = -1;
324: break;
325: }
326: }
327: rank[i] = ri;
328: }
329: }
330: // pick the best method
331: if (singleAssignableIdx >= 0) {
332: // give best single assignable match
333: return (Method) possMeths.get(singleAssignableIdx);
334: } else if (singleAssignableIdx == -1) {
335: // none assignable
336: StringBuffer sb = new StringBuffer();
337: sb.append("Method matches none of these methods: {");
338: for (int i = 0; i < nmeths; i++) {
339: sb.append("\n ");
340: sb.append(ReflectOp.toString((Method) possMeths.get(i),
341: true));
342: }
343: sb.append("\n}");
344: throw new ParseException(sb.toString());
345: } else {
346: // multiple (ambiguous) possiblities!
347: StringBuffer sb = new StringBuffer();
348: sb.append("Method is ambiguous!" + "\nPossible methods: {");
349: for (int i = 0; i < nmeths; i++) {
350: if (rank[i] >= 0) {
351: // show possible method
352: sb.append("\n ");
353: sb.append(ReflectOp.toString((Method) possMeths
354: .get(i), true));
355: }
356: }
357: sb.append("\n}");
358: throw new ParseException(sb.toString());
359: }
360: }
361:
362: public final boolean isReturnBoolean() {
363: return (getReturnClass() == Boolean.TYPE);
364: }
365:
366: public final Class getReturnClass() {
367: return meth.getReturnType();
368: }
369:
370: public final boolean execute(final Object o) {
371: Object ret = operate(o);
372: return ((Boolean) ret).booleanValue();
373: }
374:
375: public final Object operate(final Object o) {
376: for (int i = 0; i < argBuf.length; i++) {
377: argBuf[i] = (argOps[i]).operate(o);
378: }
379: try {
380: return meth.invoke(o, argBuf);
381: } catch (Exception e) {
382: throw new RuntimeException("Method \""
383: + getMethodString(true)
384: + "\" invocation resulted in Exception: " + e);
385: }
386: }
387:
388: public final void setConst(final String key, final Object val) {
389: for (int i = 0; i < argBuf.length; i++) {
390: argOps[i].setConst(key, val);
391: }
392: }
393:
394: public final String getMethodString(boolean verbose) {
395: return ReflectOp.toString(meth, verbose);
396: }
397:
398: public final void accept(TreeVisitor visitor) {
399: // (method op0 op1 .. opN)
400: visitor.visitWord(getMethodString(visitor.isVerbose()));
401: if (argOps == zeroOps) {
402: // (method)
403: } else if (argOps != null) {
404: // (method op0 op1 .. opN)
405: for (int i = 0; i < argOps.length; i++) {
406: argOps[i].accept(visitor);
407: }
408: } else {
409: // (method ("?"))
410: visitor.visitConstant(null, "?");
411: }
412: visitor.visitEnd();
413: }
414: }
|