1/*
2 * Copyright (C) 2010 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
17package com.google.doclava.apicheck;
18
19import com.google.doclava.AnnotationInstanceInfo;
20import com.google.doclava.ClassInfo;
21import com.google.doclava.Converter;
22import com.google.doclava.FieldInfo;
23import com.google.doclava.MethodInfo;
24import com.google.doclava.PackageInfo;
25import com.google.doclava.ParameterInfo;
26import com.google.doclava.SourcePositionInfo;
27import com.google.doclava.TypeInfo;
28import com.sun.javadoc.ClassDoc;
29
30import org.xml.sax.Attributes;
31import org.xml.sax.InputSource;
32import org.xml.sax.XMLReader;
33import org.xml.sax.helpers.DefaultHandler;
34import org.xml.sax.helpers.XMLReaderFactory;
35
36import java.io.InputStream;
37import java.util.ArrayList;
38import java.util.Stack;
39
40class XmlApiFile extends DefaultHandler {
41
42  private ApiInfo mApi;
43  private PackageInfo mCurrentPackage;
44  private ClassInfo mCurrentClass;
45  private AbstractMethodInfo mCurrentMethod;
46  private Stack<ClassInfo> mClassScope = new Stack<ClassInfo>();
47
48  public static ApiInfo parseApi(InputStream xmlStream) throws ApiParseException {
49    try {
50      XMLReader xmlreader = XMLReaderFactory.createXMLReader();
51      XmlApiFile handler = new XmlApiFile();
52      xmlreader.setContentHandler(handler);
53      xmlreader.setErrorHandler(handler);
54      xmlreader.parse(new InputSource(xmlStream));
55      ApiInfo apiInfo = handler.getApi();
56      apiInfo.resolveSuperclasses();
57      apiInfo.resolveInterfaces();
58      return apiInfo;
59    } catch (Exception e) {
60      throw new ApiParseException("Error parsing API", e);
61    }
62  }
63
64  private XmlApiFile() {
65    super();
66    mApi = new ApiInfo();
67  }
68
69  @Override
70  public void startElement(String uri, String localName, String qName, Attributes attributes) {
71    if (qName.equals("package")) {
72      mCurrentPackage =
73          new PackageInfo(attributes.getValue("name"), SourcePositionInfo.fromXml(attributes
74              .getValue("source")));
75    } else if (qName.equals("class") || qName.equals("interface")) {
76      // push the old outer scope for later recovery, then set
77      // up the new current class object
78      mClassScope.push(mCurrentClass);
79
80      ClassDoc classDoc = null;
81      String rawCommentText = "";
82      SourcePositionInfo position = SourcePositionInfo.fromXml(attributes.getValue("source"));
83      String visibility = attributes.getValue("visibility");
84      boolean isPublic = "public".equals(visibility);
85      boolean isProtected = "protected".equals(visibility);
86      boolean isPrivate = "private".equals(visibility);
87      boolean isPackagePrivate = !isPublic && !isPrivate && !isProtected;
88      boolean isStatic = Boolean.valueOf(attributes.getValue("static"));
89      boolean isInterface = qName.equals("interface");
90      boolean isAbstract = Boolean.valueOf(attributes.getValue("abstract"));
91      boolean isOrdinaryClass = qName.equals("class");
92      boolean isException = false; // TODO: check hierarchy for java.lang.Exception
93      boolean isError = false; // TODO: not sure.
94      boolean isEnum = false; // TODO: not sure.
95      boolean isAnnotation = false; // TODO: not sure.
96      boolean isFinal = Boolean.valueOf(attributes.getValue("final"));
97      boolean isIncluded = false;
98      String name = attributes.getValue("name");
99      String qualifiedName = qualifiedName(mCurrentPackage.name(), name, mCurrentClass);
100      String qualifiedTypeName = null; // TODO: not sure
101      boolean isPrimitive = false;
102
103      mCurrentClass =
104          new ClassInfo(classDoc, rawCommentText, position, isPublic, isProtected,
105          isPackagePrivate, isPrivate, isStatic, isInterface, isAbstract, isOrdinaryClass,
106          isException, isError, isEnum, isAnnotation, isFinal, isIncluded, name, qualifiedName,
107          qualifiedTypeName, isPrimitive);
108
109      mCurrentClass.setDeprecated("deprecated".equals(attributes.getValue("deprecated")));
110      mCurrentClass.setContainingPackage(mCurrentPackage);
111      String superclass = attributes.getValue("extends");
112      if (superclass == null && !isInterface && !"java.lang.Object".equals(qualifiedName)) {
113        throw new AssertionError("no superclass known for class " + name);
114      }
115
116      // Resolve superclass after .xml completely parsed.
117      mApi.mapClassToSuper(mCurrentClass, superclass);
118
119      TypeInfo typeInfo = Converter.obtainTypeFromString(qualifiedName) ;
120      mCurrentClass.setTypeInfo(typeInfo);
121      mCurrentClass.setAnnotations(new ArrayList<AnnotationInstanceInfo>());
122    } else if (qName.equals("method")) {
123      String rawCommentText = "";
124      ArrayList<TypeInfo> typeParameters = new ArrayList<TypeInfo>();
125      String name = attributes.getValue("name");
126      String signature = null; // TODO
127      ClassInfo containingClass = mCurrentClass;
128      ClassInfo realContainingClass = mCurrentClass;
129      String visibility = attributes.getValue("visibility");
130      boolean isPublic = "public".equals(visibility);
131      boolean isProtected = "protected".equals(visibility);
132      boolean isPrivate = "private".equals(visibility);
133      boolean isPackagePrivate = !isPublic && !isPrivate && !isProtected;
134      boolean isFinal = Boolean.valueOf(attributes.getValue("final"));
135      boolean isStatic = Boolean.valueOf(attributes.getValue("static"));
136      boolean isSynthetic = false; // TODO
137      boolean isAbstract = Boolean.valueOf(attributes.getValue("abstract"));
138      boolean isSynchronized = Boolean.valueOf(attributes.getValue("synchronized"));
139      boolean isNative = Boolean.valueOf(attributes.getValue("native"));
140      boolean isAnnotationElement = false; // TODO
141      String kind = qName;
142      String flatSignature = null; // TODO
143      MethodInfo overriddenMethod = null; // TODO
144      TypeInfo returnType = Converter.obtainTypeFromString(attributes.getValue("return"));
145      ArrayList<ParameterInfo> parameters = new ArrayList<ParameterInfo>();
146      ArrayList<ClassInfo> thrownExceptions = new ArrayList<ClassInfo>();
147      SourcePositionInfo position = SourcePositionInfo.fromXml(attributes.getValue("source"));
148      ArrayList<AnnotationInstanceInfo> annotations = new ArrayList<AnnotationInstanceInfo>(); // TODO
149
150      mCurrentMethod =
151          new MethodInfo(rawCommentText, typeParameters, name, signature, containingClass,
152          realContainingClass, isPublic, isProtected, isPackagePrivate, isPrivate, isFinal,
153          isStatic, isSynthetic, isAbstract, isSynchronized, isNative, isAnnotationElement, kind,
154          flatSignature, overriddenMethod, returnType, parameters, thrownExceptions, position,
155          annotations);
156
157      mCurrentMethod.setDeprecated("deprecated".equals(attributes.getValue("deprecated")));
158    } else if (qName.equals("constructor")) {
159      final boolean pub = "public".equals(attributes.getValue("visibility"));
160      final boolean prot = "protected".equals(attributes.getValue("visibility"));
161      final boolean pkgpriv = "".equals(attributes.getValue("visibility"));
162      mCurrentMethod =
163         new MethodInfo(""/*rawCommentText*/, new ArrayList<TypeInfo>()/*typeParameters*/,
164              attributes.getValue("name"), null/*signature*/, mCurrentClass, mCurrentClass,
165              pub, prot, pkgpriv, false/*isPrivate*/, false/*isFinal*/, false/*isStatic*/,
166              false/*isSynthetic*/, false/*isAbstract*/, false/*isSynthetic*/, false/*isNative*/,
167              false /*isAnnotationElement*/, "constructor", null/*flatSignature*/,
168              null/*overriddenMethod*/, mCurrentClass.asTypeInfo(), new ArrayList<ParameterInfo>(),
169              new ArrayList<ClassInfo>()/*thrownExceptions*/,
170              SourcePositionInfo.fromXml(attributes.getValue("source")),
171              new ArrayList<AnnotationInstanceInfo>()/*annotations*/);
172      mCurrentMethod.setDeprecated("deprecated".equals(attributes.getValue("deprecated")));
173    } else if (qName.equals("field")) {
174      String visibility = attributes.getValue("visibility");
175      boolean isPublic = visibility.equals("public");
176      boolean isProtected = visibility.equals("protected");
177      boolean isPrivate = visibility.equals("private");
178      boolean isPackagePrivate = visibility.equals("");
179      String typeName = attributes.getValue("type");
180      TypeInfo type = Converter.obtainTypeFromString(typeName);
181
182      Object value;
183      try {
184          value = ApiFile.parseValue(typeName, attributes.getValue("value"));
185      } catch (ApiParseException ex) {
186          throw new RuntimeException(ex);
187      }
188
189      FieldInfo fInfo =
190          new FieldInfo(attributes.getValue("name"), mCurrentClass, mCurrentClass, isPublic,
191          isProtected, isPackagePrivate, isPrivate, Boolean.valueOf(attributes.getValue("final")),
192          Boolean.valueOf(attributes.getValue("static")), Boolean.valueOf(attributes.
193          getValue("transient")), Boolean.valueOf(attributes.getValue("volatile")), false,
194          type, "", value, SourcePositionInfo.fromXml(attributes.getValue("source")),
195          new ArrayList<AnnotationInstanceInfo>());
196
197      fInfo.setDeprecated("deprecated".equals(attributes.getValue("deprecated")));
198      mCurrentClass.addField(fInfo);
199    } else if (qName.equals("parameter")) {
200      String name = attributes.getValue("name");
201      String typeName = attributes.getValue("type");
202      TypeInfo type = Converter.obtainTypeFromString(typeName);
203      boolean isVarArg = typeName.endsWith("...");
204      SourcePositionInfo position = null;
205
206      mCurrentMethod.addParameter(new ParameterInfo(name, typeName, type, isVarArg, position));
207      mCurrentMethod.setVarargs(isVarArg);
208    } else if (qName.equals("exception")) {
209      mCurrentMethod.addException(attributes.getValue("type"));
210    } else if (qName.equals("implements")) {
211      // Resolve interfaces after .xml completely parsed.
212      mApi.mapClassToInterface(mCurrentClass, attributes.getValue("name"));
213    }
214  }
215
216  @Override
217  public void endElement(String uri, String localName, String qName) {
218    if (qName.equals("method")) {
219      mCurrentClass.addMethod((MethodInfo) mCurrentMethod);
220    } else if (qName.equals("constructor")) {
221      mCurrentClass.addConstructor((MethodInfo) mCurrentMethod);
222    } else if (qName.equals("class") || qName.equals("interface")) {
223      mCurrentPackage.addClass(mCurrentClass);
224      mCurrentClass = mClassScope.pop();
225    } else if (qName.equals("package")) {
226      mApi.addPackage(mCurrentPackage);
227    }
228  }
229
230  public ApiInfo getApi() {
231    return mApi;
232  }
233
234  private String qualifiedName(String pkg, String className, ClassInfo parent) {
235    String parentQName = (parent != null) ? (parent.qualifiedName() + ".") : "";
236      return pkg + "." + parentQName + className;
237  }
238}
239
240