001package daikon.tools.jtb;
002
003import daikon.*;
004import java.util.ArrayList;
005import java.util.Arrays;
006import java.util.List;
007import jtb.*;
008import jtb.syntaxtree.*;
009import jtb.visitor.*;
010import org.checkerframework.checker.nullness.qual.NonNull;
011import org.checkerframework.checker.nullness.qual.Nullable;
012
013/**
014 * Matches program point names with their corresponding MethodDeclaration's (or
015 * ConstructorDeclaration's) in an AST.
016 *
017 * <p>There are a number of issues in matching, for example, ASTs contain generics, and program
018 * point names do not. This implementation handles such issues.
019 */
020public class PptNameMatcher {
021
022  // Output debugging information when matching a PptName to an AST.
023  private static boolean debug_getMatches = false;
024
025  /** Create an AST matcher that will match program points against AST elements rooted at `root'. */
026  public PptNameMatcher(Node root) {
027    root.accept(new ClassOrInterfaceTypeDecorateVisitor());
028  }
029
030  // f0 -> Modifiers()
031  // f1 -> [ "final" | Annotation() ]
032  // f2 -> Type()
033  // f3 -> [ "..." ]
034  // f4 -> VariableDeclaratorId()
035  public String getUngenerifiedType(FormalParameter p) {
036
037    Type type = p.f2;
038
039    //  Grammar production for type:
040    //  f0 -> ReferenceType()
041    //        | PrimitiveType()
042
043    if (type.f0.which == 0) {
044      // It's a reference type.
045      ReferenceType refType = (ReferenceType) type.f0.choice;
046      //  Grammar production for ReferenceType:
047      //  f0 -> PrimitiveType() ( "[" "]" )+
048      //        | ( ClassOrInterfaceType() ) ( "[" "]" )*
049
050      if (refType.f0.which == 0) {
051        // It's a primitive array; no generics to handle.
052        return Ast.getType(p);
053
054      } else {
055
056        // Make a copy of param (because we may modify it: we may
057        // remove some generics stuff).
058        // p.accept(new TreeFormatter());
059        FormalParameter param = (FormalParameter) Ast.create("FormalParameter", Ast.format(p));
060
061        Type type2 = param.f2;
062        ReferenceType refType2 = (ReferenceType) type2.f0.choice;
063
064        // Note the wrapping parentheses in
065        //    ( ClassOrInterfaceType() ) ( "[" "]" )*
066        NodeSequence intermediateSequence = (NodeSequence) refType2.f0.choice;
067        NodeSequence intermediateSequenceOrig = (NodeSequence) refType.f0.choice;
068        NodeSequence seq = (NodeSequence) intermediateSequence.elementAt(0);
069        NodeSequence seqOrig = (NodeSequence) intermediateSequenceOrig.elementAt(0);
070
071        List<Node> singleElementList = seq.nodes;
072        List<Node> singleElementListOrig = seqOrig.nodes;
073        // Replace the ClassOrInterfaceType with its ungenerified version.
074
075        //     System.out.println("@0");
076        //     param.accept(new TreeDumper());
077
078        // ClassOrInterfaceType t = (ClassOrInterfaceType)singleElementList.get(0);
079        ClassOrInterfaceType tOrig = (ClassOrInterfaceType) singleElementListOrig.get(0);
080        assert tOrig.unGenerifiedVersionOfThis != null;
081        singleElementList.set(0, tOrig.unGenerifiedVersionOfThis);
082        // Return getType of the ungenerified version of p.
083
084        // tOrig.unGenerifiedVersionOfThis may have line/col numbering
085        // that's inconsistent with param, so we call a formatter
086        // here. param is only used for matching, and afterwards it's
087        // discarded. So it's ok to reformat it.
088        param.accept(new TreeFormatter());
089
090        //     System.out.println("@1");
091        //     param.accept(new TreeDumper());
092        //     System.out.println("@2");
093
094        return Ast.getType(param);
095      }
096
097    } else {
098      // It's a primitive; no generics to handle.
099      return Ast.getType(p);
100    }
101  }
102
103  /** Iterates through program points and returns those that match the given method declaration. */
104  public List<PptTopLevel> getMatches(PptMap ppts, MethodDeclaration methdecl) {
105    return getMatchesInternal(ppts, methdecl);
106  }
107
108  /**
109   * Iterates through program points and returns those that match the given constructor declaration.
110   */
111  public List<PptTopLevel> getMatches(PptMap ppts, ConstructorDeclaration constrdecl) {
112    return getMatchesInternal(ppts, constrdecl);
113  }
114
115  // Iterates through program points and returns those that match the
116  // given method or constructor declaration.
117  private List<PptTopLevel> getMatchesInternal(PptMap ppts, Node methodOrConstructorDeclaration) {
118
119    List<PptTopLevel> result = new ArrayList<>();
120
121    for (PptTopLevel ppt : ppts.pptIterable()) {
122      PptName ppt_name = ppt.ppt_name;
123
124      if (matches(ppt_name, methodOrConstructorDeclaration)) {
125        result.add(ppt);
126      }
127    }
128
129    if (debug_getMatches) {
130      System.out.println("getMatchesInternal => " + result);
131    }
132    return result;
133  }
134
135  public boolean matches(PptName pptName, Node methodOrConstructorDeclaration) {
136
137    // This method figures out three things and then calls another
138    // method to do the match. The three things are:
139
140    // 1. method name
141    // 2. class name
142    // 3. method parameters
143
144    String classname;
145    String methodname;
146    List<FormalParameter> params;
147
148    if (methodOrConstructorDeclaration instanceof MethodDeclaration) {
149      classname = Ast.getClassName((MethodDeclaration) methodOrConstructorDeclaration);
150      methodname = Ast.getName((MethodDeclaration) methodOrConstructorDeclaration);
151      params = Ast.getParameters((MethodDeclaration) methodOrConstructorDeclaration);
152    } else if (methodOrConstructorDeclaration instanceof ConstructorDeclaration) {
153      classname = Ast.getClassName((ConstructorDeclaration) methodOrConstructorDeclaration);
154      methodname = "<init>";
155      params = Ast.getParameters((ConstructorDeclaration) methodOrConstructorDeclaration);
156    } else {
157      throw new Error(
158          "Bad type in Ast.getMatches: must be a MethodDeclaration or a ConstructorDeclaration:"
159              + methodOrConstructorDeclaration);
160    }
161
162    if (debug_getMatches) {
163      System.out.printf("getMatches(%s, %s, ...)%n", classname, methodname);
164    }
165    if (methodname.equals("<init>")) {
166      methodname = simpleName(classname);
167      if (debug_getMatches) {
168        System.out.printf("new methodname: getMatches(%s, %s, ...)%n", classname, methodname);
169      }
170    }
171
172    if (debug_getMatches) {
173      System.out.println("getMatch goal = " + classname + " " + methodname);
174    }
175
176    return matches(pptName, classname, methodname, params);
177  }
178
179  // True if pptName's name matches the method represented by the rest
180  // of the parameters.
181  private boolean matches(
182      PptName pptName, String classname, String methodname, List<FormalParameter> method_params) {
183
184    // The goal is a fully qualified classname such as
185    // samples.calculator.Calculator.AbstractOperandState, but
186    // pptName.getFullClassName() can be a binary name such as
187    // samples.calculator.Calculator$AbstractOperandState, at least for the
188    // :::OBJECT program point.  Is that a bug?
189
190    // Furthermore, pptName.getMethodName may be null for a constructor.
191
192    String pptClassName = pptName.getFullClassName();
193    boolean classname_matches =
194        (classname.equals(pptClassName)
195            || ((pptClassName != null) && classname.equals(pptClassName.replace('$', '.'))));
196    String pptMethodName = pptName.getMethodName();
197    boolean methodname_matches =
198        (methodname.equals(pptMethodName)
199            || ((pptMethodName != null)
200                && (pptMethodName.indexOf('$') >= 0)
201                && methodname.equals(pptMethodName.substring(pptMethodName.lastIndexOf('$') + 1))));
202
203    if (!(classname_matches && methodname_matches)) {
204      if (debug_getMatches) {
205        System.out.printf(
206            "getMatch: class name %s and method name %s DO NOT match candidate.%n",
207            pptClassName, pptMethodName);
208      }
209      return false;
210    }
211    if (debug_getMatches) {
212      System.out.printf(
213          "getMatch: class name %s and method name %s DO match candidate.%n",
214          classname, methodname);
215    }
216
217    List<String> pptTypeStrings = extractPptArgs(pptName);
218
219    if (pptTypeStrings.size() != method_params.size()) {
220
221      // An inner class constructor has an extra first parameter
222      // that is an implicit outer this parameter.  If so, remove it
223      // before checking for a match.
224      boolean OK_outer_this = false;
225      if (pptTypeStrings.size() == method_params.size() + 1) {
226        String icName = innerConstructorName(pptName);
227        if (icName != null) {
228          String param0Full = pptTypeStrings.get(0);
229          // String param0Simple = simpleName(pptTypeStrings.get(0));
230          // Need to check whether param0Simple is the superclass of icName.  How to do that?
231          if (classname.startsWith(param0Full + ".")) {
232            OK_outer_this = true;
233            pptTypeStrings = new ArrayList<String>(pptTypeStrings);
234            pptTypeStrings.remove(0);
235          }
236        }
237      }
238      if (!OK_outer_this) {
239        if (debug_getMatches) {
240          System.out.println(
241              "arg lengths mismatch: " + pptTypeStrings.size() + ", " + method_params.size());
242        }
243        return false;
244      }
245    }
246
247    for (int i = 0; i < pptTypeStrings.size(); i++) {
248      String pptTypeString = pptTypeStrings.get(i);
249      FormalParameter astType = method_params.get(i);
250
251      if (debug_getMatches) {
252        System.out.println(
253            "getMatch considering "
254                + pptTypeString
255                + " ("
256                + pptName.getFullClassName()
257                + ","
258                + pptName.getMethodName()
259                + ")");
260      }
261
262      if (debug_getMatches) {
263        System.out.println("Trying to match at arg position " + Integer.toString(i));
264      }
265
266      if (!typeMatch(pptTypeString, astType)) {
267        return false;
268      } else {
269        continue;
270      }
271    }
272
273    return true;
274  }
275
276  public boolean typeMatch(String pptTypeString, FormalParameter astFormalParameter) {
277
278    if (debug_getMatches) {
279      System.out.println(Ast.formatEntireTree(astFormalParameter));
280    }
281
282    String astTypeString = getUngenerifiedType(astFormalParameter);
283
284    if (debug_getMatches) {
285      System.out.println("Comparing " + pptTypeString + " to " + astTypeString + ":");
286    }
287
288    if (Ast.typeMatch(pptTypeString, astTypeString)) {
289      if (debug_getMatches) {
290        System.out.println("Match arg: " + pptTypeString + " " + astTypeString);
291      }
292      return true;
293    }
294
295    if ((pptTypeString != null) && Ast.typeMatch(pptTypeString, astTypeString)) {
296      if (debug_getMatches) {
297        System.out.println("Match arg: " + pptTypeString + " " + astTypeString);
298      }
299      return true;
300    }
301
302    if (debug_getMatches) {
303      System.out.println("Mismatch arg: " + pptTypeString + " " + astTypeString);
304    }
305
306    return false;
307  }
308
309  public List<String> extractPptArgs(PptName ppt_name) {
310
311    @SuppressWarnings("nullness") // application invariant
312    @NonNull String pptFullMethodName = ppt_name.getSignature();
313
314    if (debug_getMatches) {
315      System.out.println("in extractPptArgs: pptFullMethodName = " + pptFullMethodName);
316    }
317    int lparen = pptFullMethodName.indexOf('(');
318    int rparen = pptFullMethodName.indexOf(')');
319    assert lparen > 0;
320    assert rparen > lparen;
321    String ppt_args_string = pptFullMethodName.substring(lparen + 1, rparen);
322    String[] ppt_args = ppt_args_string.split(", ");
323    if ((ppt_args.length == 1) && ppt_args[0].equals("")) {
324      ppt_args = new String[0];
325    }
326
327    return Arrays.<String>asList(ppt_args);
328  }
329
330  /** Returns simple name of inner class, or null if ppt_name is not an inner constructor. */
331  private static @Nullable String innerConstructorName(PptName pptName) {
332    String mname = pptName.getMethodName();
333    if (mname == null) {
334      return null;
335    }
336    int dollarpos = mname.lastIndexOf('$');
337    if (dollarpos >= 0) {
338      return mname.substring(dollarpos + 1);
339    }
340    return null;
341  }
342
343  /**
344   * Returns the simple name of a possibly-fully-qualified class name. The argument can be a
345   * fully-qualified name or a binary name.
346   */
347  private static String simpleName(String classname) {
348    int dotpos = classname.lastIndexOf('.');
349    int dollarpos = classname.lastIndexOf('$');
350    int pos = Math.max(dotpos, dollarpos);
351    if (pos == -1) {
352      return classname;
353    } else {
354      return classname.substring(pos + 1);
355    }
356  }
357}