001package daikon;
002
003import java.io.IOException;
004import java.io.ObjectInputStream;
005import java.io.Serializable;
006import org.checkerframework.checker.interning.qual.Interned;
007import org.checkerframework.checker.lock.qual.GuardSatisfied;
008import org.checkerframework.checker.nullness.qual.EnsuresNonNullIf;
009import org.checkerframework.checker.nullness.qual.Nullable;
010import org.checkerframework.checker.signedness.qual.UnknownSignedness;
011import org.checkerframework.dataflow.qual.Pure;
012import org.checkerframework.dataflow.qual.SideEffectFree;
013import org.plumelib.reflection.ReflectionPlume;
014
015/**
016 * PptName is an immutable ADT that represents naming data associated with a given program point,
017 * such as the class or method.
018 *
019 * <p>Examples below are as if the full value of this PptName were
020 * "DataStructures.StackAr.pop()Ljava/lang/Object;:::EXIT84"
021 *
022 * <p>PptName is deprecated, because declaration file format 2 should not need it. Uses of PptName
023 * should be eliminated.
024 */
025// No "@Deprecated" annotation yet, but we should add it once support for
026// file format 1 is removed from Daikon.
027public class PptName implements Serializable {
028  // We are Serializable, so we specify a version to allow changes to
029  // method signatures without breaking serialization.  If you add or
030  // remove fields, you should change this number to the current date.
031  static final long serialVersionUID = 20020122L;
032
033  // These are never changed but cannot be declared "final", because they
034  // must be re-interned upon deserialization.
035  /** Full program point name. */
036  private @Interned String fullname;
037
038  // fn_name and point together comprise fullname
039  /** The part of fullname before ":::" */
040  private @Interned String fn_name;
041
042  /** Post-separator (separator is ":::") */
043  private @Interned String point;
044
045  // cls and method together comprise fn_name
046  /** Fully-qualified class name. */
047  private @Nullable @Interned String cls;
048
049  /** Method signature, including types. */
050  private final @Nullable @Interned String method;
051
052  // Representation invariant:
053  //
054  // fullname must contain ":::".
055  // fn_name is the part of fullname before ::: and point is the part after.
056  // If fn_name does contain a '(' and a '.' that comes before it, then
057  // cls is the portion before the dot and method is the portion after.
058  // If fn_name contains '(' but no dot, cls is null and method
059  // is the same as fn_name.
060  // If fn_name does not contain '(', then class is the same as fn_name and
061  // method is null.
062
063  // ==================== CONSTRUCTORS ====================
064
065  /**
066   * @param name non-null ppt name as given in the decls file
067   */
068  public PptName(String name) {
069    // If the name is well-formed, like "mvspc.setupGUI()V:::EXIT75",
070    // then this constructor will extract the class and method names.
071    // If not (eg for lisp code), it's okay because only the GUI uses
072    // this class/method information.
073
074    fullname = name.intern();
075    int separatorPosition = name.indexOf(FileIO.ppt_tag_separator);
076    if (separatorPosition == -1) {
077      throw new Daikon.UserError("no ppt_tag_separator in '" + name + "'");
078      // // probably a lisp program, which was instrumented differently
079      // cls = method = point = fn_name = null;
080      // return;
081    }
082    fn_name = name.substring(0, separatorPosition).intern();
083    point = name.substring(separatorPosition + FileIO.ppt_tag_separator.length()).intern();
084
085    int lparen = fn_name.indexOf('(');
086    if (lparen == -1) {
087      cls = fn_name;
088      method = null;
089      return;
090    }
091    int dot = fn_name.lastIndexOf('.', lparen);
092    if (dot == -1) {
093      method = fn_name;
094      cls = null;
095      return;
096    }
097    // now 0 <= dot < lparen
098    cls = fn_name.substring(0, dot).intern();
099    method = fn_name.substring(dot + 1).intern();
100  }
101
102  /**
103   * Creates a new PptName. {@code className} or {@code methodName} (or both) must be non-null.
104   *
105   * @param className fully-qualified class name
106   * @param methodName method signature, including types
107   * @param pointName post-separator (separator is ":::")
108   */
109  public PptName(@Nullable String className, @Nullable String methodName, String pointName) {
110    if ((className == null) && (methodName == null)) {
111      throw new UnsupportedOperationException("One of class or method must be non-null");
112    }
113    // First set class name
114    if (className != null) {
115      cls = className.intern();
116      fn_name = cls;
117    }
118    // Then add method name
119    if (methodName == null) {
120      method = null;
121      if (fn_name == null) {
122        throw new RuntimeException("fn_name should not be null; probably bad arguments");
123      }
124    } else {
125      method = methodName.intern();
126      if (cls != null) {
127        fn_name = (cls + "." + method).intern();
128      } else {
129        fn_name = method;
130      }
131    }
132    assert fn_name != null;
133    // Finally, add point
134    point = pointName.intern();
135    fullname = (fn_name + FileIO.ppt_tag_separator + point).intern();
136  }
137
138  // ==================== OBSERVERS ====================
139
140  /**
141   * Returns getName() [convenience accessor].
142   *
143   * @return getName() [convenience accessor]
144   * @see #getName()
145   */
146  @Pure
147  public String name() {
148    return getName();
149  }
150
151  /**
152   * Returns the complete program point name, e.g.,
153   * "DataStructures.StackAr.pop()Ljava/lang/Object;:::EXIT84".
154   *
155   * @return the complete program point name
156   */
157  @Pure
158  public String getName() {
159    return fullname;
160  }
161
162  /**
163   * Returns the fully-qualified class name, which uniquely identifies a given class. May be null.
164   * e.g. "DataStructures.StackAr".
165   *
166   * @return the fully-qualified class name
167   */
168  public @Nullable String getFullClassName() {
169    return cls;
170  }
171
172  /**
173   * Returns the short name of the class, not including any additional context, such as the package
174   * it is in. May be null. e.g. "StackAr".
175   *
176   * @return the short name of the class, or null
177   */
178  public @Nullable String getShortClassName() {
179    if (cls == null) {
180      return null;
181    }
182    int pt = cls.lastIndexOf('.');
183    if (pt == -1) {
184      return cls;
185    } else {
186      return cls.substring(pt + 1);
187    }
188  }
189
190  /**
191   * Returns a guess at the package name. May be null.
192   *
193   * @return a guess at the package name. May be null
194   */
195  public @Nullable String getPackageName() {
196    if (cls == null) {
197      return null;
198    }
199    int pt = cls.lastIndexOf('.');
200    if (pt == -1) {
201      return null;
202    } else {
203      return cls.substring(0, pt);
204    }
205  }
206
207  /**
208   * Returns the full name which can uniquely identify a method within a class. The name includes
209   * symbols for the argument types and return type. May be null. e.g. "pop()Ljava/lang/Object;".
210   *
211   * @return the full name which can uniquely identify a method within a class
212   */
213  public @Nullable String getSignature() {
214    return method;
215  }
216
217  /**
218   * Returns the name (identifier) of the method, not taking into account any arguments, return
219   * values, etc. May be null. e.g. "pop".
220   *
221   * @return the name (identifier) of the method, or null
222   */
223  public @Nullable String getMethodName() {
224    if (method == null) {
225      return null;
226    }
227    int lparen = method.indexOf('(');
228    assert lparen >= 0;
229    return method.substring(0, lparen);
230  }
231
232  /**
233   * Returns the fully-qualified class and method name (and signature). Does not include any point
234   * information (such as ENTER or EXIT). May be null. e.g.
235   * "DataStructures.StackAr.pop()Ljava/lang/Object;"
236   *
237   * @return the fully-qualified class and method name (and signature)
238   */
239  public @Nullable @Interned String getNameWithoutPoint() {
240    return fn_name;
241    // if (cls == null && method == null) {
242    //   return null;
243    // }
244    // if (cls == null) { return method; }
245    // if (method == null) { return cls; }
246    // return (cls + "." + method).intern();
247  }
248
249  /**
250   * Returns something interesting and descriptive about this point, along the lines of "ENTER" or
251   * "EXIT" or some such. The semantics of this method are not yet decided, so don't try to do
252   * anything useful with this result. May be null. e.g. "EXIT84".
253   *
254   * @return something interesting and descriptive about this
255   */
256  public @Nullable String getPoint() {
257    return point;
258  }
259
260  /**
261   * Returns a numerical subscript of the given point, or Integer.MIN_VALUE if none exists. e.g.
262   * "84".
263   *
264   * @return a numerical subscript of the given point, or Integer.MIN_VALUE if none exists
265   * @see #exitLine()
266   */
267  public int getPointSubscript() {
268    int result = Integer.MIN_VALUE;
269    if (point != null) {
270      // returns the largest substring [i..] which parses to an integer
271      for (int i = 0; i < point.length(); i++) {
272        char c = point.charAt(i);
273        if (('0' <= c) && (c <= '9')) {
274          try {
275            result = Integer.parseInt(point.substring(i));
276            break;
277          } catch (NumberFormatException e) {
278            continue;
279          }
280        }
281      }
282    }
283    return result;
284  }
285
286  /**
287   * Returns true iff this name refers to a synthetic object instance program point.
288   *
289   * @return true iff this name refers to a synthetic object instance program point
290   */
291  @Pure
292  public boolean isObjectInstanceSynthetic() {
293    return FileIO.object_suffix.equals(point);
294  }
295
296  /**
297   * Returns true iff this name refers to a synthetic class instance program point.
298   *
299   * @return true iff this name refers to a synthetic class instance program point
300   */
301  @Pure
302  public boolean isClassStaticSynthetic() {
303    return FileIO.class_static_suffix.equals(point);
304  }
305
306  /**
307   * Returns true iff this name refers to program globals.
308   *
309   * @return true iff this name refers to program globals
310   */
311  @Pure
312  public boolean isGlobalPoint() {
313    return FileIO.global_suffix.equals(point);
314  }
315
316  /**
317   * Returns true iff this name refers to a procedure exit point.
318   *
319   * @return true iff this name refers to a procedure exit point
320   */
321  @EnsuresNonNullIf(result = true, expression = "point")
322  @Pure
323  public boolean isExitPoint() {
324    return (point != null) && point.startsWith(FileIO.exit_suffix);
325  }
326
327  /**
328   * Returns true iff this name refers to an abrupt completion point.
329   *
330   * @return true iff this name refers to an abrupt completion point
331   */
332  @EnsuresNonNullIf(result = true, expression = "point")
333  @Pure
334  public boolean isThrowsPoint() {
335    return (point != null) && point.startsWith(FileIO.throws_suffix);
336  }
337
338  /**
339   * Returns true iff this name refers to a combined (synthetic) procedure exit point.
340   *
341   * @return true iff this name refers to a combined (synthetic) procedure exit point
342   */
343  @EnsuresNonNullIf(result = true, expression = "point")
344  @Pure
345  public boolean isCombinedExitPoint() {
346    return (point != null) && point.equals(FileIO.exit_suffix);
347  }
348
349  /**
350   * Returns true iff this name refers to an actual (not combined) procedure exit point (eg,
351   * EXIT22).
352   *
353   * @return true iff this name refers to an actual (not combined) procedure exit point
354   */
355  @EnsuresNonNullIf(result = true, expression = "point")
356  @Pure
357  public boolean isNumberedExitPoint() {
358    return (point != null) && (isExitPoint() && !isCombinedExitPoint());
359  }
360
361  /**
362   * Returns true iff this name refers to a procedure exit point.
363   *
364   * @return true iff this name refers to a procedure exit point
365   */
366  @EnsuresNonNullIf(result = true, expression = "point")
367  @Pure
368  public boolean isEnterPoint() {
369    return (point != null) && point.startsWith(FileIO.enter_suffix);
370  }
371
372  /**
373   * Returns a string containing the line number, if this is an exit point; otherwise, returns an
374   * empty string.
375   *
376   * @return a string containing the line number, if this is an exit point; otherwise, return an
377   *     empty string
378   * @see #getPointSubscript()
379   */
380  public String exitLine() {
381    if (!isExitPoint()) {
382      return "";
383    }
384    int non_digit;
385    for (non_digit = FileIO.exit_suffix.length(); non_digit < point.length(); non_digit++) {
386      if (!Character.isDigit(point.charAt(non_digit))) {
387        break;
388      }
389    }
390    return point.substring(FileIO.exit_suffix.length(), non_digit);
391  }
392
393  /**
394   * Returns true iff this program point is a constructor entry or exit. There are two ways in which
395   * this works. With the older declaration format, the method name starts with {@code <init>}. The
396   * newer declaration format does not have {@code <init>} but their method name includes the class
397   * name. For compatibility both mechanisms are checked.
398   *
399   * @return true iff this program point is a constructor entry or exit
400   */
401  @Pure
402  public boolean isConstructor() {
403
404    if (method != null) {
405
406      if (method.startsWith("<init>")) {
407        return true;
408      }
409
410      if (cls == null) {
411        return false;
412      }
413
414      @SuppressWarnings("signature") // cls is allowed to be arbitrary, especially for non-Java code
415      String class_name = ReflectionPlume.fullyQualifiedNameToSimpleName(cls);
416      assert method != null; // for nullness checker
417      int arg_start = method.indexOf('(');
418      String method_name = method;
419      if (arg_start != -1) {
420        method_name = method.substring(0, arg_start);
421      }
422
423      // System.out.println ("fullname = " + fullname);
424      // System.out.println ("fn_name = " + fn_name);
425      // System.out.println ("method = " + method);
426      // System.out.println ("cls = " + cls);
427      // System.out.println ("class_name = " + class_name);
428      // System.out.println ("method_name = " + method_name);
429
430      if (class_name.equals(method_name)) {
431        return true;
432      }
433    }
434
435    return false;
436  }
437
438  /**
439   * Debugging output.
440   *
441   * @return a string representation of this
442   */
443  public String repr(@UnknownSignedness PptName this) {
444    return "PptName: fullname="
445        + fullname
446        + "; fn_name="
447        + fn_name
448        + "; point="
449        + point
450        + "; cls="
451        + cls
452        + "; method="
453        + method;
454  }
455
456  // ==================== PRODUCERS ====================
457
458  /**
459   * Requires: this.isExitPoint()
460   *
461   * @return a name for the corresponding enter point
462   */
463  public PptName makeEnter() {
464    // This associates throw points with the main entry point.
465    // We may wish to have a different exceptional than non-exceptional
466    // entry point; in particular, if there was an exception, then perhaps
467    // the precondition or object invariant was not met.
468    assert isExitPoint() : fullname;
469
470    assert isExitPoint() || isThrowsPoint();
471    return new PptName(cls, method, FileIO.enter_suffix);
472  }
473
474  /**
475   * Requires: this.isExitPoint() || this.isEnterPoint()
476   *
477   * @return a name for the combined exit point
478   */
479  public PptName makeExit() {
480    assert isExitPoint() || isEnterPoint() : fullname;
481    return new PptName(cls, method, FileIO.exit_suffix);
482  }
483
484  /**
485   * Requires: this.isExitPoint() || this.isEnterPoint()
486   *
487   * @return a name for the corresponding object invariant
488   */
489  public PptName makeObject() {
490    assert isExitPoint() || isEnterPoint() : fullname;
491    return new PptName(cls, null, FileIO.object_suffix);
492  }
493
494  /**
495   * Requires: this.isExitPoint() || this.isEnterPoint() || this.isObjectInstanceSynthetic()
496   *
497   * @return a name for the corresponding class-static invariant
498   */
499  public PptName makeClassStatic() {
500    assert isExitPoint() || isEnterPoint() || isObjectInstanceSynthetic() : fullname;
501    return new PptName(cls, null, FileIO.class_static_suffix);
502  }
503
504  // ==================== OBJECT METHODS ====================
505
506  /* @return interned string such that this.equals(new PptName(this.toString())) */
507  @SideEffectFree
508  @Override
509  public String toString(@GuardSatisfied PptName this) {
510    return fullname;
511  }
512
513  @EnsuresNonNullIf(result = true, expression = "#1")
514  @Pure
515  @Override
516  public boolean equals(@GuardSatisfied PptName this, @GuardSatisfied @Nullable Object o) {
517    return (o instanceof PptName) && equalsPptName((PptName) o);
518  }
519
520  @EnsuresNonNullIf(result = true, expression = "#1")
521  @Pure
522  public boolean equalsPptName(@GuardSatisfied PptName this, @GuardSatisfied PptName o) {
523    return (o != null) && (o.fullname == fullname);
524  }
525
526  @Pure
527  @Override
528  public int hashCode(@GuardSatisfied @UnknownSignedness PptName this) {
529    return fullname.hashCode();
530  }
531
532  // Interning is lost when an object is serialized and deserialized.
533  // Manually re-intern any interned fields upon deserialization.
534  private void readObject(ObjectInputStream in) throws IOException, ClassNotFoundException {
535    try {
536      in.defaultReadObject();
537      if (fullname != null) fullname = fullname.intern();
538      if (fn_name != null) fn_name = fn_name.intern();
539      if (cls != null) cls = cls.intern();
540      if (method != null) {
541        // method = method.intern();
542        ReflectionPlume.setFinalField(this, "method", method.intern());
543      }
544      if (point != null) point = point.intern();
545    } catch (NoSuchFieldException e) {
546      throw new Error(e);
547    }
548  }
549}