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