1
+ /*
2
+ * Copyright (C) 2013 Google, Inc.
3
+ *
4
+ * Licensed under the Apache License, Version 2.0 (the "License");
5
+ * you may not use this file except in compliance with the License.
6
+ * You may obtain a copy of the License at
7
+ *
8
+ * http://www.apache.org/licenses/LICENSE-2.0
9
+ *
10
+ * Unless required by applicable law or agreed to in writing, software
11
+ * distributed under the License is distributed on an "AS IS" BASIS,
12
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13
+ * See the License for the specific language governing permissions and
14
+ * limitations under the License.
15
+ */
16
+ package com .google .auto .value ;
17
+
18
+ import java .util .ArrayDeque ;
19
+ import java .util .ArrayList ;
20
+ import java .util .Deque ;
21
+ import java .util .HashMap ;
22
+ import java .util .List ;
23
+ import java .util .Map ;
24
+
25
+ /**
26
+ * An ultrasimplified Java parser for {@link EclipseHack} that examines classes to extract just
27
+ * the abstract methods. The parsing is very superficial. It assumes that the source text is
28
+ * syntactically correct, which it must be in the context of an annotation processor because the
29
+ * compiler doesn't invoke the processor if there are syntax errors.
30
+ *
31
+ * <p>We recognize the text {@code ... class Foo ... { ... } } as a class called Foo, whose
32
+ * definition extends to the matching right brace. Within a class definition, we recognize the text
33
+ * {@code abstract ... bar ( ) } as an abstract method called bar.
34
+ *
35
+ * <p>We construct a {@code Map<String, List<String>>} that represents the abstract methods found in
36
+ * each class, in the order they were found. If com.example.Foo contains a nested class Bar, then
37
+ * there will be an entry for "com.example.Foo.Bar" in this Map.
38
+ *
39
+ * @author emcmanus@google.com (Éamonn McManus)
40
+ */
41
+ final class AbstractMethodExtractor {
42
+ AbstractMethodExtractor () {}
43
+
44
+ // Here are the details of the matching. We track the current brace depth, and we artificially
45
+ // consider that the whole file is at brace depth 1 inside a pseudo-class whose name is the
46
+ // name of the package. When we see a class definition, we push the fully-qualified name of the
47
+ // class on a stack so that we can associate abstract methods we find with the possibly-nested
48
+ // class they belong to. A class definition must occur at brace depth one more than the class
49
+ // containing it, which is equivalent to saying that the brace depth must be the same as the
50
+ // class stack depth. This check excludes local class definitions within methods and
51
+ // initializers. If we meet these constraints and we see the word "class" followed by an
52
+ // identifier Foo, then we consider that we are entering the definition of class Foo. We determine
53
+ // the fully-qualified name of Foo, which is container.Foo, where container is the current top of
54
+ // the class stack (initially, the package name). We push this new fully-qualified name on the
55
+ // class stack. We have not yet seen the left brace with the class definition so at this point the
56
+ // class stack depth is one more than the brace depth. When we subsequently see a right brace that
57
+ // takes us back to this situation then we know we have completed the definition of Foo and we can
58
+ // pop it from the class stack.
59
+ //
60
+ // We check that the token after "class" is indeed an identifier to avoid confusion
61
+ // with Foo.class. Even though the tokenizer does not distinguish between identifiers and
62
+ // keywords, it is enough to exclude the single word "instanceof" because that is the only word
63
+ // that can legally appear after Foo.class (though in a legal program the resultant expression
64
+ // will always be true).
65
+ //
66
+ // Again, we are at the top level of a class when the brace depth is equal to the class stack
67
+ // depth. If we then see the word "abstract" then that is the start either of an abstract class
68
+ // definition or of an abstract method. We record that we have seen "abstract" and we cancel
69
+ // that indication as soon as we see a left brace, to exclude the abstract class case, and also
70
+ // the case of interfaces or @interfaces redundantly declared abstract. Now, when
71
+ // we see an identifier that is preceded by an uncanceled "abstract" and followed by a left paren
72
+ // then we have found an abstract method of the class on the top of the class stack. We record it
73
+ // in the list of abstract methods of the class on the top of the class stack. We don't bother
74
+ // checking that the method has no parameters, because an @AutoValue class will cause a compiler
75
+ // error if there are abstract methods with parameters, since the @AutoValue processor doesn't
76
+ // know how to implement them in the concrete subclass it generates.
77
+ Map <String , List <String >> abstractMethods (JavaTokenizer tokenizer , String packageName ) {
78
+ Map <String , List <String >> abstractMethods = new HashMap <String , List <String >>();
79
+ Deque <String > classStack = new ArrayDeque <String >();
80
+ classStack .addLast (packageName );
81
+ int braceDepth = 1 ;
82
+ boolean sawAbstract = false ;
83
+ String className = null ;
84
+ for (String previousToken = "" , token = tokenizer .nextToken ();
85
+ token != null ;
86
+ previousToken = token , token = tokenizer .nextToken ()) {
87
+ boolean topLevel = (braceDepth == classStack .size ());
88
+ if (className != null ) {
89
+ // get last term in fully-qualified class name (e.g. "class some.package.Bar { ...")
90
+ if (token .equals ("." )) {
91
+ className = tokenizer .nextToken ();
92
+ continue ;
93
+ } else {
94
+ if (Character .isJavaIdentifierStart (className .charAt (0 ))
95
+ && !className .equals ("instanceof" )) {
96
+ String container = classStack .getLast ();
97
+ classStack .addLast (container + "." + className );
98
+ }
99
+ className = null ;
100
+ }
101
+ }
102
+ if (token .equals ("{" )) {
103
+ braceDepth ++;
104
+ sawAbstract = false ;
105
+ } else if (token .equals ("}" )) {
106
+ braceDepth --;
107
+ if (topLevel ) {
108
+ classStack .removeLast ();
109
+ }
110
+ } else if (topLevel ) {
111
+ if (token .equals ("class" )) {
112
+ className = tokenizer .nextToken ();
113
+ } else if (token .equals ("abstract" )) {
114
+ sawAbstract = true ;
115
+ } else if (token .equals ("(" )) {
116
+ if (sawAbstract && Character .isJavaIdentifierStart (previousToken .charAt (0 ))) {
117
+ List <String > methods = abstractMethods .get (classStack .getLast ());
118
+ if (methods == null ) {
119
+ methods = new ArrayList <String >();
120
+ abstractMethods .put (classStack .getLast (), methods );
121
+ }
122
+ methods .add (previousToken );
123
+ }
124
+ sawAbstract = false ;
125
+ }
126
+ }
127
+ }
128
+ return abstractMethods ;
129
+ }
130
+ }
0 commit comments