001package daikon.tools.jtb;
002
003import java.io.StringWriter;
004import java.util.ArrayDeque;
005import java.util.ArrayList;
006import java.util.Deque;
007import java.util.HashMap;
008import java.util.List;
009import java.util.Map;
010import jtb.syntaxtree.*;
011import jtb.visitor.*;
012import org.checkerframework.checker.nullness.qual.KeyFor;
013
014/** Replaces uses of generic type parameters with versions that do not use generics. */
015public class ClassOrInterfaceTypeDecorateVisitor extends DepthFirstVisitor {
016
017  // A map from token images to "ungenerified" versions of the classes
018  // or interfaces that the given identifiers extend.
019  HashMap<String, Deque<ClassOrInterfaceType>> shadowingMap = new HashMap<>();
020
021  // For debugging purposes.
022  @SuppressWarnings("UnusedMethod") // debugging code is currently commented out
023  private void printShadowingMap() {
024    System.out.println("Shadowing map:");
025    for (Map.Entry<@KeyFor("shadowingMap") String, Deque<ClassOrInterfaceType>> e :
026        shadowingMap.entrySet()) {
027      System.out.print("  " + e.getKey() + " stack: ");
028      for (ClassOrInterfaceType t : e.getValue()) {
029        StringWriter w = new StringWriter();
030        t.accept(new TreeFormatter());
031        t.accept(new TreeDumper(w));
032        System.out.print(w.toString().trim() + " ");
033      }
034      System.out.println();
035    }
036  }
037
038  // f0 -> [ TypeParameters() ]
039  // f1 -> ResultType()
040  // f2 -> MethodDeclarator()
041  // f3 -> [ "throws" NameList() ]
042  // f4 -> ( Block() | ";" )
043  @Override
044  public void visit(MethodDeclaration n) {
045
046    // A shallow clone, which is what we want.
047    HashMap<String, Deque<ClassOrInterfaceType>> oldShadowingMap = copy(shadowingMap);
048
049    if (n.f0.present()) {
050      augmentShadowingMap((TypeParameters) n.f0.node);
051    }
052    n.f1.accept(this);
053    n.f2.accept(this);
054    n.f3.accept(this);
055    n.f4.accept(this);
056
057    // Restore shadowing map because we're going out of scope from
058    // the TypeParameters declared in this method.
059    shadowingMap = oldShadowingMap;
060    // printShadowingMap();
061  }
062
063  // f0 -> [ TypeParameters() ]
064  // f1 -> <IDENTIFIER>
065  // f2 -> FormalParameters()
066  // f3 -> [ "throws" NameList() ]
067  // f4 -> "{"
068  // f5 -> [ ExplicitConstructorInvocation() ]
069  // f6 -> ( BlockStatement() )*
070  // f7 -> "}"
071  @Override
072  public void visit(ConstructorDeclaration n) {
073
074    // A shallow clone, which is what we want.
075    HashMap<String, Deque<ClassOrInterfaceType>> oldShadowingMap = copy(shadowingMap);
076
077    if (n.f0.present()) {
078      augmentShadowingMap((TypeParameters) n.f0.node);
079    }
080    n.f1.accept(this);
081    n.f2.accept(this);
082    n.f3.accept(this);
083    n.f4.accept(this);
084    n.f5.accept(this);
085    n.f6.accept(this);
086    n.f7.accept(this);
087
088    // Restore shadowing map because we're going out of scope from
089    // the TypeParameters declared in this method.
090    shadowingMap = oldShadowingMap;
091    // printShadowingMap();
092  }
093
094  // f0 -> ( "class" | "interface" )
095  // f1 -> <IDENTIFIER>
096  // f2 -> [ TypeParameters() ]
097  // f3 -> [ ExtendsList(isInterface) ]
098  // f4 -> [ ImplementsList(isInterface) ]
099  // f5 -> ClassOrInterfaceBody(isInterface)
100  @Override
101  public void visit(ClassOrInterfaceDeclaration n) {
102
103    // A shallow clone, which is what we want.
104    HashMap<String, Deque<ClassOrInterfaceType>> oldShadowingMap = copy(shadowingMap);
105
106    n.f0.accept(this);
107    n.f1.accept(this);
108
109    if (n.f2.present()) {
110      augmentShadowingMap((TypeParameters) n.f2.node);
111    }
112
113    n.f2.accept(this);
114    n.f3.accept(this);
115    n.f4.accept(this);
116    n.f5.accept(this);
117
118    // Restore shadowing map because we're going out of scope from
119    // the TypeParameters declared in this method.
120    shadowingMap = oldShadowingMap;
121    // printShadowingMap();
122  }
123
124  public void augmentShadowingMap(TypeParameters n) {
125    // Grammar production:
126    // f0 -> "<"
127    // f1 -> TypeParameter()
128    // f2 -> ( "," TypeParameter() )*
129    // f3 -> ">"
130    final List<TypeParameter> params = new ArrayList<>();
131    n.accept(
132        new DepthFirstVisitor() {
133          @Override
134          public void visit(TypeParameter n) {
135            params.add(n);
136          }
137        });
138
139    for (TypeParameter t : params) {
140      augmentShadowingMap(t);
141    }
142  }
143
144  // f0 -> <IDENTIFIER>
145  // f1 -> [ TypeBound() ]
146  public void augmentShadowingMap(TypeParameter n) {
147    n.f0.accept(this);
148    n.f1.accept(this);
149    TypeBound b = (TypeBound) n.f1.node;
150
151    if (n.f1.present()) {
152
153      // Grammar production for TypeBound:
154      // f0 -> "extends"
155      // f1 -> ClassOrInterfaceType()
156      // f2 -> ( "&" ClassOrInterfaceType() )*
157
158      // TODO figure out how/whether to handle f2 (currently it's just ignored).
159
160      assert b.f1.unGenerifiedVersionOfThis != null;
161
162      Deque<ClassOrInterfaceType> s =
163          shadowingMap.computeIfAbsent(
164              n.f0.tokenImage, __ -> new ArrayDeque<ClassOrInterfaceType>());
165      s.push(b.f1.unGenerifiedVersionOfThis);
166
167    } else {
168
169      // No explicit bound means that bound is java.lang.Object.
170
171      Deque<ClassOrInterfaceType> s =
172          shadowingMap.computeIfAbsent(
173              n.f0.tokenImage, __ -> new ArrayDeque<ClassOrInterfaceType>());
174
175      ClassOrInterfaceType objectType =
176          (ClassOrInterfaceType) Ast.create("ClassOrInterfaceType", "Object");
177      s.push(objectType);
178    }
179
180    // printShadowingMap();
181  }
182
183  // ClassOrInterfaceType:
184  // f0 -> <IDENTIFIER>
185  // f1 -> [ TypeArguments() ]
186  // f2 -> ( "." <IDENTIFIER> [ TypeArguments() ] )*
187  @Override
188  public void visit(ClassOrInterfaceType t) {
189    t.f0.accept(this);
190    t.f1.accept(this); // NO NEED TO DO THIS?
191    t.f2.accept(this);
192
193    // Make a copy of the ClassOrInterfaceType.
194    StringWriter w = new StringWriter();
195    // t.accept(new TreeFormatter());
196    t.accept(new TreeDumper(w));
197    ClassOrInterfaceType n =
198        (ClassOrInterfaceType) Ast.create("ClassOrInterfaceType", w.toString());
199
200    ungenerify(n);
201
202    t.unGenerifiedVersionOfThis = n;
203  }
204
205  // ClassOrInterfaceType:
206  // f0 -> <IDENTIFIER>
207  // f1 -> [ TypeArguments() ]
208  // f2 -> ( "." <IDENTIFIER> [ TypeArguments() ] )*
209  @SuppressWarnings("JdkObsolete") // JTB uses Vector
210  private void ungenerify(ClassOrInterfaceType n) {
211
212    // Drop all type arguments.
213    n.f1 = new NodeOptional(); // This removes optional node, if present.
214    List<Node> nodeSequenceList = n.f2.nodes;
215    for (int i = 0; i < nodeSequenceList.size(); i++) {
216      NodeSequence oldSequence = (NodeSequence) nodeSequenceList.get(i);
217      NodeSequence newSequence = new NodeSequence(3);
218      newSequence.addNode(oldSequence.elementAt(0)); // "."
219      newSequence.addNode(oldSequence.elementAt(1)); // <IDENTIFIER>
220      newSequence.addNode(new NodeOptional()); // get rid of type arguments
221      n.f2.nodes.set(i, newSequence);
222    }
223
224    // 2. Only the first <IDENTIFIER> may possibly be associated
225    //    with a type argument. If we find it in typeParametersInScope,
226    //    we replace t with [...]
227    for (Map.Entry<@KeyFor("shadowingMap") String, Deque<ClassOrInterfaceType>> entry :
228        shadowingMap.entrySet()) {
229      if (entry.getKey().equals(n.f0.tokenImage)) {
230        ClassOrInterfaceType c = entry.getValue().getFirst();
231        // System.out.println("c:" + Ast.format(c));
232        List<Node> cSequence = c.f2.nodes;
233        // System.out.print("cSequence:");
234        // for (Node n2 : cSequence) {
235        //   System.out.print(Ast.format(n2) + " ");
236        // }
237        // Prepend all-but-first identifiers to the list of identifiers in f2.
238        // Prepending in reverse order ensures the right prepending order.
239        for (int i = cSequence.size() - 1; i >= 0; i--) {
240          nodeSequenceList.add(0, cSequence.get(i));
241        }
242        // Set f0 to the first identifier.
243        n.f0 = c.f0;
244      }
245    }
246
247    {
248      // StringWriter sw = new StringWriter();
249      // n.accept(new TreeFormatter());
250      // t.accept(new TreeDumper(sw));
251      // System.out.print("t::::");
252      // System.out.println(sw.toString().trim());
253    }
254    {
255      // StringWriter sw = new StringWriter();
256      // n.accept(new TreeFormatter());
257      // n.accept(new TreeDumper(sw));
258      // System.out.print("n::::");
259      // System.out.println(sw.toString().trim());
260    }
261  }
262
263  // Makes a copy of the stacks and of the map. The
264  // ClassOrInterfaceType objects are not copied.
265  private static HashMap<String, Deque<ClassOrInterfaceType>> copy(
266      HashMap<String, Deque<ClassOrInterfaceType>> m) {
267
268    HashMap<String, Deque<ClassOrInterfaceType>> newMap = new HashMap<>();
269
270    for (Map.Entry<@KeyFor("m") String, Deque<ClassOrInterfaceType>> e : m.entrySet()) {
271      String key = e.getKey();
272      Deque<ClassOrInterfaceType> oldStack = e.getValue();
273      Deque<ClassOrInterfaceType> newStack =
274          new ArrayDeque<ClassOrInterfaceType>(oldStack); // clone
275      newMap.put(key, newStack);
276    }
277
278    return newMap;
279  }
280}