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