1   // Copyright 2012- Bill Campbell, Swami Iyer and Bahar Akbal-Delibas
2   
3   package jminusminus;
4   
5   import java.lang.reflect.Array;
6   import java.lang.reflect.Modifier;
7   
8   import java.util.Arrays;
9   import java.util.ArrayList;
10  import java.util.Hashtable;
11  
12  /**
13   * A class for representing j-- types. All types are represented underneath (in the classRep
14   * field) by Java objects of type Class. These objects represent types in Java, so this should
15   * ease our interfacing with existing Java classes.
16   * <p>
17   * Class types (reference types that are represented by the identifiers introduced in class
18   * declarations) are represented using TypeName. So for now, every TypeName represents a class.
19   * In the future, TypeName could be extended to represent interfaces or enumerations.
20   * <p>
21   * IdentifierTypes must be "resolved" at some point, so that all Types having the same name refer
22   * to the same Type object. The resolve() method does this.
23   */
24  class Type {
25      // The Type's internal (Java) representation.
26      private Class<?> classRep;
27  
28      // Maps type names to their Type representations.
29      private static Hashtable<String, Type> types = new Hashtable<String, Type>();
30  
31      /**
32       * The int type.
33       */
34      public final static Type INT = typeFor(int.class);
35  
36      /**
37       * The char type.
38       */
39      public final static Type CHAR = typeFor(char.class);
40  
41      /**
42       * The boolean type.
43       */
44      public final static Type BOOLEAN = typeFor(boolean.class);
45  
46      /**
47       * The long type.
48       */
49      public final static Type LONG = typeFor(long.class);
50  
51      /**
52       * The double type.
53       */
54      public final static Type DOUBLE = typeFor(double.class);
55  
56      /**
57       * The java.lang.Integer type.
58       */
59      public final static Type BOXED_INT = typeFor(java.lang.Integer.class);
60  
61      /**
62       * The java.lang.Character type.
63       */
64      public final static Type BOXED_CHAR = typeFor(java.lang.Character.class);
65  
66      /**
67       * The java.lang.Boolean type.
68       */
69      public final static Type BOXED_BOOLEAN = typeFor(java.lang.Boolean.class);
70  
71      /**
72       * The java.lang.Long type.
73       */
74      public final static Type BOXED_LONG = typeFor(java.lang.Long.class);
75  
76      /**
77       * The java.lang.Double type.
78       */
79      public final static Type BOXED_DOUBLE = typeFor(java.lang.Double.class);
80  
81      /**
82       * The java.lang.String type.
83       */
84      public final static Type STRING = typeFor(java.lang.String.class);
85  
86      /**
87       * The java.lang.Object type.
88       */
89      public final static Type OBJECT = typeFor(java.lang.Object.class);
90  
91      /**
92       * The void type.
93       */
94      public final static Type VOID = typeFor(void.class);
95  
96      /**
97       * The null type.
98       */
99      public final static Type NULLTYPE = new Type(java.lang.Object.class);
100 
101     /**
102      * The "any" type (denotes wild expressions).
103      */
104     public final static Type ANY = new Type(null);
105 
106     /**
107      * A type marker indicating a constructor (having no return type).
108      */
109     public final static Type CONSTRUCTOR = new Type(null);
110 
111     /**
112      * This constructor is to keep the compiler happy.
113      */
114     protected Type() {
115         super();
116     }
117 
118     /**
119      * Constructs and returns a representation for a type from its (Java) class representation,
120      * making sure there is a unique representation for each unique type.
121      *
122      * @param classRep the Java class representation.
123      * @return a type representation of classRep.
124      */
125     public static Type typeFor(Class<?> classRep) {
126         if (types.get(descriptorFor(classRep)) == null) {
127             types.put(descriptorFor(classRep), new Type(classRep));
128         }
129         return types.get(descriptorFor(classRep));
130     }
131 
132     /**
133      * Returns the class representation for this type.
134      *
135      * @return the class representation for this type.
136      */
137     public Class<?> classRep() {
138         return classRep;
139     }
140 
141     /**
142      * Sets the class representation of this type to the specified partial class.
143      *
144      * @param classRep the partial class.
145      */
146     public void setClassRep(Class<?> classRep) {
147         this.classRep = classRep;
148     }
149 
150     /**
151      * Returns true if this type has the same descriptor as other, and false otherwise.
152      *
153      * @param other the other type.
154      * @return true if this type has the same descriptor as other, and false otherwise.
155      */
156     public boolean equals(Type other) {
157         return this.toDescriptor().equals(other.toDescriptor());
158     }
159 
160     /**
161      * Returns true if this is an array type, and false otherwise.
162      *
163      * @return true if this is an array type, and false otherwise.
164      */
165     public boolean isArray() {
166         return classRep.isArray();
167     }
168 
169     /**
170      * Returns an array type's component type.
171      *
172      * @return an array type's component type.
173      */
174     public Type componentType() {
175         return typeFor(classRep.getComponentType());
176     }
177 
178     /**
179      * Returns this type's super type, or null.
180      *
181      * @return this type's super type, or null.
182      */
183     public Type superClass() {
184         return classRep == null || classRep.getSuperclass() == null ? null :
185                 typeFor(classRep.getSuperclass());
186     }
187 
188     /**
189      * Returns true if this is a primitive type, and false otherwise.
190      *
191      * @return true if this is a primitive type, and false otherwise.
192      */
193     public boolean isPrimitive() {
194         return classRep.isPrimitive();
195     }
196 
197     /**
198      * Returns true if this is an interface type, and false otherwise.
199      *
200      * @return true if this is an interface type, and false otherwise.
201      */
202     public boolean isInterface() {
203         return classRep.isInterface();
204     }
205 
206     /**
207      * Returns true if this is a reference type, and false otherwise.
208      *
209      * @return true if this is a reference type, and false otherwise.
210      */
211     public boolean isReference() {
212         return !isPrimitive();
213     }
214 
215     /**
216      * Returns true of this type is declared final, and false otherwise.
217      *
218      * @return true of this type is declared final, and false otherwise.
219      */
220     public boolean isFinal() {
221         return Modifier.isFinal(classRep.getModifiers());
222     }
223 
224     /**
225      * Returns true of this type is declared abstract, and false otherwise.
226      *
227      * @return true of this type is declared abstract, and false otherwise.
228      */
229     public boolean isAbstract() {
230         return Modifier.isAbstract(classRep.getModifiers());
231     }
232 
233     /**
234      * Returns true if this is a supertype of other, and false otherwise.
235      *
236      * @param that the candidate subtype.
237      * @return true if this is a supertype of other, and false otherwise.
238      */
239     public boolean isJavaAssignableFrom(Type that) {
240         return this.classRep.isAssignableFrom(that.classRep);
241     }
242 
243     /**
244      * Returns a list of this class' abstract methods.
245      * <p>
246      * It has abstract methods if:
247      * <ol>
248      *   <li>Any method declared in the class is abstract or
249      *   <li>its superclass has an abstract method which is not overridden here.
250      * </ol>
251      *
252      * @return a list of this class' abstract methods.
253      */
254     public ArrayList<Method> abstractMethods() {
255         ArrayList<Method> inheritedAbstractMethods = superClass() == null ? new ArrayList<Method>()
256                 : superClass().abstractMethods();
257         ArrayList<Method> abstractMethods = new ArrayList<Method>();
258         ArrayList<Method> declaredConcreteMethods = declaredConcreteMethods();
259         ArrayList<Method> declaredAbstractMethods = declaredAbstractMethods();
260         abstractMethods.addAll(declaredAbstractMethods);
261         for (Method method : inheritedAbstractMethods) {
262             if (!declaredConcreteMethods.contains(method) &&
263                     !declaredAbstractMethods.contains(method)) {
264                 abstractMethods.add(method);
265             }
266         }
267         return abstractMethods;
268     }
269 
270     /**
271      * Returns a list of this class' declared abstract methods.
272      *
273      * @return a list of this class' declared abstract methods.
274      */
275     private ArrayList<Method> declaredAbstractMethods() {
276         ArrayList<Method> declaredAbstractMethods = new ArrayList<Method>();
277         for (java.lang.reflect.Method method : classRep.getDeclaredMethods()) {
278             if (Modifier.isAbstract(method.getModifiers())) {
279                 declaredAbstractMethods.add(new Method(method));
280             }
281         }
282         return declaredAbstractMethods;
283     }
284 
285     /**
286      * Returns a list of this class' declared concrete methods.
287      *
288      * @return a list of this class' declared concrete methods.
289      */
290     private ArrayList<Method> declaredConcreteMethods() {
291         ArrayList<Method> declaredConcreteMethods = new ArrayList<Method>();
292         for (java.lang.reflect.Method method : classRep.getDeclaredMethods()) {
293             if (!Modifier.isAbstract(method.getModifiers())) {
294                 declaredConcreteMethods.add(new Method(method));
295             }
296         }
297         return declaredConcreteMethods;
298     }
299 
300     /**
301      * An assertion that this type matches one of the specified types. If there is no match, an
302      * error is reported.
303      *
304      * @param line          the line near which the mismatch occurs.
305      * @param expectedTypes expected types.
306      */
307     public void mustMatchOneOf(int line, Type... expectedTypes) {
308         if (this == Type.ANY) {
309             return;
310         }
311         for (Type type : expectedTypes) {
312             if (matchesExpected(type)) {
313                 return;
314             }
315         }
316         JAST.compilationUnit.reportSemanticError(line,
317                 "Type %s doesn't match any of the expected types %s", this,
318                 Arrays.toString(expectedTypes));
319     }
320 
321     /**
322      * An assertion that this type matches the specified type. If there is no match, an error is
323      * reported.
324      *
325      * @param line         the line near which the mismatch occurs.
326      * @param expectedType type with which to match.
327      */
328     public void mustMatchExpected(int line, Type expectedType) {
329         if (!matchesExpected(expectedType)) {
330             JAST.compilationUnit.reportSemanticError(line, "Type %s doesn't match type %s", this,
331                     expectedType);
332         }
333     }
334 
335     /**
336      * Returns true if this type matches expected, and false otherwise.
337      *
338      * @param expected the type that this might match.
339      * @return true if this type matches expected, and false otherwise.
340      */
341     public boolean matchesExpected(Type expected) {
342         return this == Type.ANY || expected == Type.ANY ||
343                 (this == Type.NULLTYPE && expected.isReference()) || this.equals(expected);
344     }
345 
346     /**
347      * Returns true if the argument types match, and false otherwise.
348      *
349      * @param argTypes1 arguments (classReps) of one method.
350      * @param argTypes2 arguments (classReps) of another method.
351      * @return true if the argument types match, and false otherwise.
352      */
353     public static boolean argTypesMatch(Class<?>[] argTypes1, Class<?>[] argTypes2) {
354         if (argTypes1.length != argTypes2.length) {
355             return false;
356         }
357         for (int i = 0; i < argTypes1.length; i++) {
358             if (!Type.descriptorFor(argTypes1[i]).equals(Type.descriptorFor(argTypes2[i]))) {
359                 return false;
360             }
361         }
362         return true;
363     }
364 
365     /**
366      * Returns the simple (unqualified) name of this type.
367      *
368      * @return the simple (unqualified) name of this type.
369      */
370     public String simpleName() {
371         return classRep.getSimpleName();
372     }
373 
374     /**
375      * Returns a string representation of this type.
376      *
377      * @return a string representation of this type.
378      */
379     public String toString() {
380         return toJava(this.classRep);
381     }
382 
383     /**
384      * Returns the JVM descriptor of this type.
385      *
386      * @return the JVM descriptor of this type.
387      */
388     public String toDescriptor() {
389         return descriptorFor(classRep);
390     }
391 
392     /**
393      * Returns the JVM representation of this type's name.
394      *
395      * @return the JVM representation of this type's name.
396      */
397     public String jvmName() {
398         return this.isArray() || this.isPrimitive() ?
399                 this.toDescriptor() : classRep.getName().replace('.', '/');
400     }
401 
402     /**
403      * Returns this type's package name.
404      *
405      * @return this type's package name.
406      */
407     public String packageName() {
408         String name = toString();
409         return name.lastIndexOf('.') == -1 ? "" : name.substring(0, name.lastIndexOf('.') - 1);
410     }
411 
412     /**
413      * Returns a string representation for a type being appended to a StringBuffer (for the + and
414      * += operations over strings).
415      *
416      * @return a string representation for a type being appended to a StringBuffer.
417      */
418     public String argumentTypeForAppend() {
419         return this == Type.STRING || this.isPrimitive() ? toDescriptor() : "Ljava/lang/Object;";
420     }
421 
422     /**
423      * Finds and returns a method in this type having the given name and argument types, or null.
424      *
425      * @param name     the method name.
426      * @param argTypes the argument types.
427      * @return a method in this type having the given name and argument types, or null.
428      */
429     public Method methodFor(String name, Type[] argTypes) {
430         Class[] classes = new Class[argTypes.length];
431         for (int i = 0; i < argTypes.length; i++) {
432             classes[i] = argTypes[i].classRep;
433         }
434         Class cls = classRep;
435 
436         // Search this class and all superclasses.
437         while (cls != null) {
438             java.lang.reflect.Method[] methods = cls.getDeclaredMethods();
439             for (java.lang.reflect.Method method : methods) {
440                 if (method.getName().equals(name) && Type.argTypesMatch(classes,
441                         method.getParameterTypes())) {
442                     return new Method(method);
443                 }
444             }
445             cls = cls.getSuperclass();
446         }
447 
448         return null;
449     }
450 
451     /**
452      * Finds and returns a constructor in this type having the given argument types, or null.
453      *
454      * @param argTypes the argument types.
455      * @return a constructor in this type having the given argument types, or null.
456      */
457     public Constructor constructorFor(Type[] argTypes) {
458         Class[] classes = new Class[argTypes.length];
459         for (int i = 0; i < argTypes.length; i++) {
460             classes[i] = argTypes[i].classRep;
461         }
462 
463         // Search only this class (we don't inherit constructors).
464         java.lang.reflect.Constructor[] constructors = classRep.getDeclaredConstructors();
465         for (java.lang.reflect.Constructor constructor : constructors) {
466             if (argTypesMatch(classes, constructor.getParameterTypes())) {
467                 return new Constructor(constructor);
468             }
469         }
470 
471         return null;
472     }
473 
474     /**
475      * Finds and returns a field in this type having the given name, or null.
476      *
477      * @param name the name of the field we want.
478      * @return a field in this type having the given name, or null.
479      */
480     public Field fieldFor(String name) {
481         Class<?> cls = classRep;
482         while (cls != null) {
483             java.lang.reflect.Field[] fields = cls.getDeclaredFields();
484             for (java.lang.reflect.Field field : fields) {
485                 if (field.getName().equals(name)) {
486                     return new Field(field);
487                 }
488             }
489             cls = cls.getSuperclass();
490         }
491         return null;
492     }
493 
494     /**
495      * Returns a string representation of an array of argument types.
496      *
497      * @param argTypes the array of argument types.
498      * @return a string representation of an array of argument types.
499      */
500     public static String argTypesAsString(Type[] argTypes) {
501         if (argTypes.length == 0) {
502             return "()";
503         } else {
504             String str = "(" + argTypes[0].toString();
505             for (int i = 1; i < argTypes.length; i++) {
506                 str += "," + argTypes[i];
507             }
508             str += ")";
509             return str;
510         }
511     }
512 
513     /**
514      * Returns true if the member is accessible from this type, and false otherwise.
515      *
516      * @param line   the line in which the access occurs.
517      * @param member the member being accessed.
518      * @return true if the member is accessible from this type, and false otherwise.
519      */
520     public boolean checkAccess(int line, Member member) {
521         if (!checkAccess(line, classRep, member.declaringType().classRep)) {
522             return false;
523         }
524         // The member must be either public, protected, or private.
525         if (member.isPublic()) {
526             return true;
527         }
528         java.lang.Package p1 = classRep.getPackage();
529         java.lang.Package p2 = member.declaringType().classRep.getPackage();
530         if ((p1 == null ? "" : p1.getName()).equals((p2 == null ? "" : p2.getName()))) {
531             return true;
532         }
533         if (member.isProtected()) {
534             if (classRep.getPackage().getName().equals(
535                     member.declaringType().classRep.getPackage().getName())
536                     || typeFor(member.getClass().getDeclaringClass())
537                     .isJavaAssignableFrom(this)) {
538                 return true;
539             } else {
540                 JAST.compilationUnit.reportSemanticError(line,
541                         "The protected member, " + member.name() + ", is not accessible.");
542                 return false;
543             }
544         }
545         if (member.isPrivate()) {
546             if (descriptorFor(classRep).equals(
547                     descriptorFor(member.member().getDeclaringClass()))) {
548                 return true;
549             } else {
550                 JAST.compilationUnit.reportSemanticError(line,
551                         "The private member, " + member.name() + ", is not accessible.");
552                 return false;
553             }
554         }
555 
556         // Otherwise, the member has default access.
557         if (packageName().equals(member.declaringType().packageName())) {
558             return true;
559         } else {
560             JAST.compilationUnit.reportSemanticError(line, "The member, " + member.name() +
561                     ", is not accessible because it's in a different package.");
562             return false;
563         }
564     }
565 
566     /**
567      * Returns true if the target type is accessible from this type, and false otherwise.
568      *
569      * @param line       line in which the access occurs.
570      * @param targetType the type being accessed.
571      * @return true if the target type is accessible from this type, and false otherwise.
572      */
573     public boolean checkAccess(int line, Type targetType) {
574         if (targetType.isPrimitive()) {
575             return true;
576         }
577         if (targetType.isArray()) {
578             return this.checkAccess(line, targetType.componentType());
579         }
580         return checkAccess(line, classRep, targetType.classRep);
581     }
582 
583     /**
584      * Returns true if the referenced type is accessible from the referencing type, and false
585      * otherwise.
586      *
587      * @param line            the line in which the access occurs.
588      * @param referencingType the type attempting the access.
589      * @param type            the type that we want to access.
590      * @return true if the referenced type is accessible from the referencing type, and false
591      * otherwise.
592      */
593     public static boolean checkAccess(int line, Class referencingType, Class type) {
594         java.lang.Package p1 = referencingType.getPackage();
595         java.lang.Package p2 = type.getPackage();
596         if (Modifier.isPublic(type.getModifiers()) ||
597                 (p1 == null ? "" : p1.getName()).equals((p2 == null ? "" : p2.getName()))) {
598             return true;
599         } else {
600             JAST.compilationUnit.reportSemanticError(line, "The type, " + type.getCanonicalName() +
601                     ", is not accessible from " + referencingType.getCanonicalName());
602             return false;
603         }
604     }
605 
606     /**
607      * Resolves this type in the given context and returns the resolved type.
608      *
609      * @param context context in which the names are resolved.
610      * @return the resolved type.
611      */
612     public Type resolve(Context context) {
613         return this;
614     }
615 
616     /**
617      * Returns a signature for reporting unfound methods and constructors.
618      *
619      * @param name     the message or type name.
620      * @param argTypes the actual argument types.
621      * @return a signature for reporting unfound methods and constructors.
622      */
623     public static String signatureFor(String name, Type[] argTypes) {
624         String signature = name + "(";
625         if (argTypes.length > 0) {
626             signature += argTypes[0].toString();
627             for (int i = 1; i < argTypes.length; i++) {
628                 signature += "," + argTypes[i].toString();
629             }
630         }
631         signature += ")";
632         return signature;
633     }
634 
635     // Constructs a representation for a type from its Java (Class) representation. Use typeFor()
636     // that maps types having like classReps to like Types.
637     private Type(Class<?> classRep) {
638         this.classRep = classRep;
639     }
640 
641     // Returns the JVM descriptor of a type's class representation.
642     private static String descriptorFor(Class<?> classRep) {
643         return classRep == null ? "V" : classRep == void.class ? "V"
644                 : classRep.isArray() ? "[" + descriptorFor(classRep.getComponentType())
645                 : classRep.isPrimitive() ? (classRep == int.class ? "I"
646                 : classRep == char.class ? "C"
647                 : classRep == boolean.class ? "Z"
648                 : classRep == double.class ? "D"
649                 : classRep == long.class ? "J" : "?")
650                 : "L" + classRep.getName().replace('.', '/') + ";";
651     }
652 
653     // Returns the Java (and so j--) denotation for the specified type.
654     private static String toJava(Class classRep) {
655         return classRep.isArray() ? toJava(classRep.getComponentType()) + "[]" : classRep.getName();
656     }
657 }
658 
659 /**
660  * A representation of any reference type that can be denoted as a (possibly qualified) identifier.
661  */
662 class TypeName extends Type {
663     // The line in which the identifier occurs in the source file.
664     private int line;
665 
666     // The identifier's name.
667     private String name;
668 
669     /**
670      * Constructs a TypeName.
671      *
672      * @param line the line in which the identifier occurs in the source file.
673      * @param name fully qualified name for the identifier.
674      */
675     public TypeName(int line, String name) {
676         this.line = line;
677         this.name = name;
678     }
679 
680     /**
681      * Returns the line in which the identifier occurs in the source file.
682      *
683      * @return the line in which the identifier occurs in the source file.
684      */
685     public int line() {
686         return line;
687     }
688 
689     /**
690      * {@inheritDoc}
691      */
692     public String jvmName() {
693         return name.replace('.', '/');
694     }
695 
696     /**
697      * {@inheritDoc}
698      */
699     public String toDescriptor() {
700         return "L" + jvmName() + ";";
701     }
702 
703     /**
704      * {@inheritDoc}
705      */
706     public String toString() {
707         return name;
708     }
709 
710     /**
711      * {@inheritDoc}
712      */
713     public String simpleName() {
714         return name.substring(name.lastIndexOf('.') + 1);
715     }
716 
717     /**
718      * {@inheritDoc}
719      */
720     public Type resolve(Context context) {
721         Type resolvedType = context.lookupType(name);
722         if (resolvedType == null) {
723             // Try loading a type with the given fullname.
724             try {
725                 resolvedType = typeFor(Class.forName(name));
726                 context.addType(line, resolvedType);
727             } catch (Exception e) {
728                 JAST.compilationUnit.reportSemanticError(line, "Unable to locate %s", name);
729                 resolvedType = Type.ANY;
730             }
731         }
732         return resolvedType;
733     }
734 }
735 
736 /**
737  * A representation of an array type. It is built by the Parser to stand in for a Type until the
738  * analyze() phase, at which point it is resolved to an actual Type object (having a Class that
739  * identifies it).
740  */
741 class ArrayTypeName extends Type {
742     // The array's base or component type.
743     private Type componentType;
744 
745     /**
746      * Constructs an ArrayTypeName given its component type.
747      *
748      * @param componentType the type of the array's elements.
749      */
750     public ArrayTypeName(Type componentType) {
751         this.componentType = componentType;
752     }
753 
754     /**
755      * {@inheritDoc}
756      */
757     public Type componentType() {
758         return componentType;
759     }
760 
761     /**
762      * {@inheritDoc}
763      */
764     public String toDescriptor() {
765         return "[" + componentType.toDescriptor();
766     }
767 
768     /**
769      * {@inheritDoc}
770      */
771     public String toString() {
772         return componentType.toString() + "[]";
773     }
774 
775     /**
776      * {@inheritDoc}
777      */
778     public Type resolve(Context context) {
779         componentType = componentType.resolve(context);
780         Class classRep = Array.newInstance(componentType().classRep(), 0).getClass();
781         return Type.typeFor(classRep);
782     }
783 }
784