From dc3f6e66f549d0313192d6df2bd3b2b3fa9b70a9 Mon Sep 17 00:00:00 2001 From: Xavier Ducrohet Date: Tue, 22 May 2012 16:27:36 -0700 Subject: Update ApiGenerator to only deal with android.jar. Change-Id: Ia47944b3e5420859913201c8e430b6b53841ee1c --- .../com/android/apigenerator/AndroidJarReader.java | 253 +++++++++ .../src/com/android/apigenerator/ApiClass.java | 32 +- .../android/apigenerator/ApiParseException.java | 59 -- .../src/com/android/apigenerator/EnumParser.java | 147 ----- .../src/com/android/apigenerator/Main.java | 99 +--- .../src/com/android/apigenerator/NewApiParser.java | 619 --------------------- .../src/com/android/apigenerator/ParserState.java | 174 ------ .../src/com/android/apigenerator/XmlApiParser.java | 126 ----- .../apigenerator/enumfix/AndroidJarReader.java | 132 ----- .../src/com/android/apigenerator/enums.xml | 596 -------------------- 10 files changed, 290 insertions(+), 1947 deletions(-) create mode 100644 apigenerator/src/com/android/apigenerator/AndroidJarReader.java delete mode 100644 apigenerator/src/com/android/apigenerator/ApiParseException.java delete mode 100644 apigenerator/src/com/android/apigenerator/EnumParser.java delete mode 100644 apigenerator/src/com/android/apigenerator/NewApiParser.java delete mode 100644 apigenerator/src/com/android/apigenerator/ParserState.java delete mode 100644 apigenerator/src/com/android/apigenerator/XmlApiParser.java delete mode 100644 apigenerator/src/com/android/apigenerator/enumfix/AndroidJarReader.java delete mode 100644 apigenerator/src/com/android/apigenerator/enums.xml diff --git a/apigenerator/src/com/android/apigenerator/AndroidJarReader.java b/apigenerator/src/com/android/apigenerator/AndroidJarReader.java new file mode 100644 index 0000000..89924a5 --- /dev/null +++ b/apigenerator/src/com/android/apigenerator/AndroidJarReader.java @@ -0,0 +1,253 @@ +/* + * Copyright (C) 2012 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package com.android.apigenerator; + +import com.android.util.Pair; + +import org.objectweb.asm.ClassReader; +import org.objectweb.asm.Opcodes; +import org.objectweb.asm.tree.ClassNode; +import org.objectweb.asm.tree.FieldNode; +import org.objectweb.asm.tree.MethodNode; + +import java.io.File; +import java.io.FileInputStream; +import java.io.FileNotFoundException; +import java.io.IOException; +import java.net.MalformedURLException; +import java.util.HashMap; +import java.util.List; +import java.util.Map; +import java.util.Map.Entry; +import java.util.zip.ZipEntry; +import java.util.zip.ZipInputStream; + +/** + * Reads all the android.jar files found in an SDK and generate a map of {@link ApiClass}. + * + */ +public class AndroidJarReader { + + private static final byte[] BUFFER = new byte[65535]; + + private final String mSdkFolder; + + public AndroidJarReader(String sdkFolder) { + mSdkFolder = sdkFolder; + } + + public Map getClasses() { + HashMap map = new HashMap(); + + // Get all the android.jar. They are in platforms-# + int apiLevel = 0; + while (true) { + apiLevel++; + try { + File jar = new File(mSdkFolder, "platforms/android-" + apiLevel + "/android.jar"); + if (jar.exists() == false) { + System.out.println("Last API level found: " + (apiLevel-1)); + break; + } + + FileInputStream fis = new FileInputStream(jar); + ZipInputStream zis = new ZipInputStream(fis); + ZipEntry entry = zis.getNextEntry(); + while (entry != null) { + String name = entry.getName(); + + if (name.endsWith(".class")) { + + int index = 0; + do { + int size = zis.read(BUFFER, index, BUFFER.length - index); + if (size >= 0) { + index += size; + } else { + break; + } + } while (true); + + byte[] b = new byte[index]; + System.arraycopy(BUFFER, 0, b, 0, index); + + ClassReader reader = new ClassReader(b); + ClassNode classNode = new ClassNode(); + reader.accept(classNode, 0 /*flags*/); + + if (classNode != null) { + ApiClass theClass = addClass(map, classNode.name, apiLevel); + + // super class + if (classNode.superName != null) { + theClass.addSuperClass(classNode.superName, apiLevel); + } + + // interfaces + for (Object interfaceName : classNode.interfaces) { + theClass.addInterface((String) interfaceName, apiLevel); + } + + // fields + for (Object field : classNode.fields) { + FieldNode fieldNode = (FieldNode) field; + if ((fieldNode.access & Opcodes.ACC_PRIVATE) != 0) { + continue; + } + if (fieldNode.name.startsWith("this$") == false && + fieldNode.name.equals("$VALUES") == false) { + theClass.addField(fieldNode.name, apiLevel); + } + } + + // methods + for (Object method : classNode.methods) { + MethodNode methodNode = (MethodNode) method; + if ((methodNode.access & Opcodes.ACC_PRIVATE) != 0) { + continue; + } + if (methodNode.name.equals("") == false) { + theClass.addMethod(methodNode.name + methodNode.desc, apiLevel); + } + } + } + } + entry = zis.getNextEntry(); + } + + } catch (MalformedURLException e) { + e.printStackTrace(); + } catch (FileNotFoundException e) { + e.printStackTrace(); + } catch (IOException e) { + e.printStackTrace(); + } finally { + + } + } + + postProcessClasses(map); + + return map; + } + + private void postProcessClasses(Map classes) { + for (ApiClass theClass : classes.values()) { + Map methods = theClass.getMethods(); + Map fixedMethods = new HashMap(); + + List> superClasses = theClass.getSuperClasses(); + List> interfaces = theClass.getInterfaces(); + + methodLoop: for (Entry method : methods.entrySet()) { + String methodName = method.getKey(); + int apiLevel = method.getValue(); + + if (methodName.startsWith("(") == false) { + + for (Pair parent : superClasses) { + // only check the parent if it was a parent class at the introduction + // of the method. + if (parent.getSecond() <= apiLevel) { + ApiClass parentClass = classes.get(parent.getFirst()); + assert parentClass != null; + if (parentClass != null && + checkClassContains(theClass.getName(), + methodName, apiLevel, + classes, parentClass)) { + continue methodLoop; + } + } + } + + for (Pair parent : interfaces) { + // only check the parent if it was a parent class at the introduction + // of the method. + if (parent.getSecond() <= apiLevel) { + ApiClass parentClass = classes.get(parent.getFirst()); + assert parentClass != null; + if (parentClass != null && + checkClassContains(theClass.getName(), + methodName, apiLevel, + classes, parentClass)) { + continue methodLoop; + } + } + } + } + + // if we reach here. the method isn't an override + fixedMethods.put(methodName, method.getValue()); + } + + theClass.replaceMethods(fixedMethods); + } + } + + private boolean checkClassContains(String className, String methodName, int apiLevel, + Map classMap, ApiClass parentClass) { + + Integer parentMethodApiLevel = parentClass.getMethods().get(methodName); + if (parentMethodApiLevel != null && parentMethodApiLevel <= apiLevel) { + // the parent class has the method and it was introduced in the parent at the + // same api level as the method, or before. + return true; + } + + // check on this class parents. + List> superClasses = parentClass.getSuperClasses(); + List> interfaces = parentClass.getInterfaces(); + + for (Pair parent : superClasses) { + // only check the parent if it was a parent class at the introduction + // of the method. + if (parent.getSecond() <= apiLevel) { + ApiClass superParentClass = classMap.get(parent.getFirst()); + assert superParentClass != null; + if (superParentClass != null && checkClassContains(className, methodName, apiLevel, + classMap, superParentClass)) { + return true; + } + } + } + + for (Pair parent : interfaces) { + // only check the parent if it was a parent class at the introduction + // of the method. + if (parent.getSecond() <= apiLevel) { + ApiClass superParentClass = classMap.get(parent.getFirst()); + assert superParentClass != null; + if (superParentClass != null && checkClassContains(className, methodName, apiLevel, + classMap, superParentClass)) { + return true; + } + } + } + + return false; + } + + private ApiClass addClass(HashMap classes, String name, int apiLevel) { + ApiClass theClass = classes.get(name); + if (theClass == null) { + theClass = new ApiClass(name, apiLevel); + classes.put(name, theClass); + } + + return theClass; + } +} diff --git a/apigenerator/src/com/android/apigenerator/ApiClass.java b/apigenerator/src/com/android/apigenerator/ApiClass.java index 13b2d42..ccdc075 100644 --- a/apigenerator/src/com/android/apigenerator/ApiClass.java +++ b/apigenerator/src/com/android/apigenerator/ApiClass.java @@ -20,6 +20,8 @@ import com.android.util.Pair; import java.io.PrintStream; import java.util.ArrayList; +import java.util.Collections; +import java.util.Comparator; import java.util.HashMap; import java.util.List; import java.util.Map; @@ -49,6 +51,10 @@ public class ApiClass { mSince = since; } + public String getName() { + return mName; + } + int getSince() { return mSince; } @@ -67,18 +73,35 @@ public class ApiClass { } } + public Map getMethods() { + return mMethods; + } + + public void replaceMethods(Map fixedMethods) { + mMethods.clear(); + mMethods.putAll(fixedMethods); + } + public void addSuperClass(String superClass, int since) { addToArray(mSuperClasses, superClass, since); } + public List> getSuperClasses() { + return mSuperClasses; + } + public void addInterface(String interfaceClass, int since) { addToArray(mInterfaces, interfaceClass, since); } + public List> getInterfaces() { + return mInterfaces; + } + void addToArray(List> list, String name, int value) { // check if we already have that name (at a lower level) for (Pair pair : list) { - if (name.equals(pair.getFirst())) { + if (name.equals(pair.getFirst()) && pair.getSecond() < value) { return; } } @@ -102,6 +125,13 @@ public class ApiClass { } private void print(List > list, String name, PrintStream stream) { + Collections.sort(list, new Comparator >() { + + @Override + public int compare(Pair o1, Pair o2) { + return o1.getFirst().compareTo(o2.getFirst()); + } + }); for (Pair pair : list) { if (mSince == pair.getSecond()) { diff --git a/apigenerator/src/com/android/apigenerator/ApiParseException.java b/apigenerator/src/com/android/apigenerator/ApiParseException.java deleted file mode 100644 index 7fc8bde..0000000 --- a/apigenerator/src/com/android/apigenerator/ApiParseException.java +++ /dev/null @@ -1,59 +0,0 @@ -/* - * Copyright (C) 2012 The Android Open Source Project - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ - -package com.android.apigenerator; - - -/** - * Basic exception used by {@link NewApiParser}. - * - * This is adapted from doclava. - * - */ -public final class ApiParseException extends Exception { - private static final long serialVersionUID = 1L; - - public String file; - public int line; - - public ApiParseException() { - } - - public ApiParseException(String message) { - super(message); - } - - public ApiParseException(String message, Exception cause) { - super(message, cause); - if (cause instanceof ApiParseException) { - this.line = ((ApiParseException) cause).line; - } - } - - public ApiParseException(String message, int line) { - super(message); - this.line = line; - } - - @Override - public String getMessage() { - if (line > 0) { - return super.getMessage() + " line " + line; - } else { - return super.getMessage(); - } - } -} diff --git a/apigenerator/src/com/android/apigenerator/EnumParser.java b/apigenerator/src/com/android/apigenerator/EnumParser.java deleted file mode 100644 index 18c0a94..0000000 --- a/apigenerator/src/com/android/apigenerator/EnumParser.java +++ /dev/null @@ -1,147 +0,0 @@ -/* - * Copyright (C) 2012 The Android Open Source Project - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ - -package com.android.apigenerator; - -import org.xml.sax.Attributes; -import org.xml.sax.SAXException; -import org.xml.sax.helpers.DefaultHandler; - -import java.io.IOException; -import java.io.InputStream; -import java.util.HashMap; -import java.util.Map; - -import javax.xml.parsers.ParserConfigurationException; -import javax.xml.parsers.SAXParser; -import javax.xml.parsers.SAXParserFactory; - -/** - * Parser for the simplified XML API format version 1. - */ -public class EnumParser extends DefaultHandler { - - private final static String NODE_API = "api"; - private final static String NODE_CLASS = "class"; - private final static String NODE_FIELD = "field"; - private final static String NODE_METHOD = "method"; - private final static String NODE_EXTENDS = "extends"; - private final static String NODE_IMPLEMENTS = "implements"; - - private final static String ATTR_NAME = "name"; - private final static String ATTR_SINCE = "since"; - - private final Map mClasses = new HashMap(); - - private ApiClass mCurrentClass; - - public EnumParser() { - } - - public Map getClasses() { - return mClasses; - } - - @Override - public void startElement(String uri, String localName, String qName, Attributes attributes) - throws SAXException { - - if (localName == null || localName.length() == 0) { - localName = qName; - } - - try { - if (NODE_API.equals(localName)) { - // do nothing. - - } else if (NODE_CLASS.equals(localName)) { - String name = attributes.getValue(ATTR_NAME); - int since = Integer.parseInt(attributes.getValue(ATTR_SINCE)); - - mCurrentClass = addClass(name, since); - - } else if (NODE_EXTENDS.equals(localName)) { - String name = attributes.getValue(ATTR_NAME); - int since = getSince(attributes); - - mCurrentClass.addSuperClass(name, since); - - } else if (NODE_IMPLEMENTS.equals(localName)) { - String name = attributes.getValue(ATTR_NAME); - int since = getSince(attributes); - - mCurrentClass.addInterface(name, since); - - } else if (NODE_METHOD.equals(localName)) { - String name = attributes.getValue(ATTR_NAME); - int since = getSince(attributes); - - mCurrentClass.addMethod(name, since); - - } else if (NODE_FIELD.equals(localName)) { - String name = attributes.getValue(ATTR_NAME); - int since = getSince(attributes); - - mCurrentClass.addField(name, since); - - } - - } finally { - super.startElement(uri, localName, qName, attributes); - } - } - - private ApiClass addClass(String name, int apiLevel) { - ApiClass theClass = mClasses.get(name); - if (theClass == null) { - theClass = new ApiClass(name, apiLevel); - mClasses.put(name, theClass); - } - - return theClass; - } - - private int getSince(Attributes attributes) { - int since = mCurrentClass.getSince(); - String sinceAttr = attributes.getValue(ATTR_SINCE); - - if (sinceAttr != null) { - since = Integer.parseInt(sinceAttr); - } - - return since; - } - - public static Map parseApi(InputStream stream) { - try { - SAXParserFactory parserFactory = SAXParserFactory.newInstance(); - SAXParser parser = parserFactory.newSAXParser(); - EnumParser apiParser = new EnumParser(); - parser.parse(stream, apiParser); - - return apiParser.getClasses(); - } catch (ParserConfigurationException e) { - e.printStackTrace(); - } catch (SAXException e) { - e.printStackTrace(); - } catch (IOException e) { - e.printStackTrace(); - } - - return null; - } - -} diff --git a/apigenerator/src/com/android/apigenerator/Main.java b/apigenerator/src/com/android/apigenerator/Main.java index 5c26e14..4ce7ac9 100644 --- a/apigenerator/src/com/android/apigenerator/Main.java +++ b/apigenerator/src/com/android/apigenerator/Main.java @@ -17,24 +17,13 @@ package com.android.apigenerator; -import com.android.apigenerator.enumfix.AndroidJarReader; - -import org.xml.sax.SAXException; import java.io.File; -import java.io.FileInputStream; import java.io.FileNotFoundException; -import java.io.IOException; -import java.io.InputStream; import java.io.PrintStream; -import java.util.HashMap; import java.util.Map; import java.util.TreeMap; -import javax.xml.parsers.ParserConfigurationException; -import javax.xml.parsers.SAXParser; -import javax.xml.parsers.SAXParserFactory; - /** * Main class for command line command to convert the existing API XML/TXT files into diff-based * simple text files. @@ -46,98 +35,22 @@ public class Main { * @param args */ public static void main(String[] args) { - if (args.length < 2 || args.length > 3) { + if (args.length != 2) { printUsage(); } - if (args.length == 3) { - if (args[0].equals("enum")) { - AndroidJarReader reader = new AndroidJarReader(args[1]); - Map classes = reader.getEnumClasses(); - createApiFile(new File(args[2]), classes); - } else { - printUsage(); - } - } else { - Map classes = parsePlatformApiFiles(new File(args[0])); - createApiFile(new File(args[1]), classes); - } - + AndroidJarReader reader = new AndroidJarReader(args[0]); + Map classes = reader.getClasses(); + createApiFile(new File(args[1]), classes); } private static void printUsage() { - System.err.println("Convert API files into a more manageable file\n"); + System.err.println("Generates a single API file from the content of an SDK.\n"); System.err.println("Usage\n"); - System.err.println("\tApiCheck [enum] FOLDER OUTFILE\n"); + System.err.println("\tApiCheck SDKFOLDER OUTFILE\n"); System.exit(1); } - - /** - * Parses platform API files. - * @param apiFolder the folder containing the files. - * @return a top level {@link ApiInfo} object for the highest available API level. - */ - private static Map parsePlatformApiFiles(File apiFolder) { - int apiLevel = 1; - - Map map = new HashMap(); - - InputStream stream = Main.class.getResourceAsStream( - "enums.xml"); - if (stream != null) { - map = EnumParser.parseApi(stream); - } - - if (map == null) { - map = new HashMap(); - } - - while (true) { - File file = new File(apiFolder, Integer.toString(apiLevel) + ".xml"); - if (file.exists()) { - parseXmlApiFile(file, apiLevel, map); - apiLevel++; - } else { - file = new File(apiFolder, Integer.toString(apiLevel) + ".txt"); - if (file.exists()) { - parseTxtApiFile(file, apiLevel, map); - apiLevel++; - - } else { - break; - } - } - } - - return map; - } - - private static void parseTxtApiFile(File apiFile, int api, Map map) { - try { - NewApiParser.parseApi(apiFile.getName(), new FileInputStream(apiFile), map, api); - } catch (FileNotFoundException e) { - e.printStackTrace(); - } catch (ApiParseException e) { - e.printStackTrace(); - } - } - - private static void parseXmlApiFile(File apiFile, int apiLevel, - Map map) { - try { - SAXParserFactory parserFactory = SAXParserFactory.newInstance(); - SAXParser parser = parserFactory.newSAXParser(); - parser.parse(new FileInputStream(apiFile), new XmlApiParser(map, apiLevel)); - } catch (ParserConfigurationException e) { - e.printStackTrace(); - } catch (SAXException e) { - e.printStackTrace(); - } catch (IOException e) { - e.printStackTrace(); - } - } - /** * Creates the simplified diff-based API level. * @param outFolder the out folder. diff --git a/apigenerator/src/com/android/apigenerator/NewApiParser.java b/apigenerator/src/com/android/apigenerator/NewApiParser.java deleted file mode 100644 index 91cb1e2..0000000 --- a/apigenerator/src/com/android/apigenerator/NewApiParser.java +++ /dev/null @@ -1,619 +0,0 @@ -/* - * Copyright (C) 2012 The Android Open Source Project - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ - -package com.android.apigenerator; - -import java.io.IOException; -import java.io.InputStream; -import java.util.Map; - -/** - * Parser for the new format of platform API files. This is adapted from the Doclava code. - * - */ -class NewApiParser { - - public static void parseApi(String filename, InputStream stream, - Map classes, int api) throws ApiParseException { - final int CHUNK = 1024 * 1024; - int hint = 0; - try { - hint = stream.available() + CHUNK; - } catch (IOException ex) { - } - - if (hint < CHUNK) { - hint = CHUNK; - } - - byte[] buf = new byte[hint]; - int size = 0; - - try { - while (true) { - if (size == buf.length) { - byte[] tmp = new byte[buf.length + CHUNK]; - System.arraycopy(buf, 0, tmp, 0, buf.length); - buf = tmp; - } - int amt = stream.read(buf, size, (buf.length - size)); - if (amt < 0) { - break; - } else { - size += amt; - } - } - } catch (IOException ex) { - throw new ApiParseException("Error reading API file", ex); - } - - final Tokenizer tokenizer = new Tokenizer(filename, - (new String(buf, 0, size)).toCharArray()); - - final ParserState state = new ParserState(classes, api); - - while (true) { - String token = tokenizer.getToken(); - if (token == null) { - break; - } - if ("package".equals(token)) { - parsePackage(state, tokenizer); - } else { - throw new ApiParseException("expected package got " + token, tokenizer.getLine()); - } - } - } - - private static void parsePackage(ParserState state, Tokenizer tokenizer) - throws ApiParseException { - String token; - String name; - - token = tokenizer.requireToken(); - assertIdent(tokenizer, token); - name = token; - - state.addPackage(name); - - token = tokenizer.requireToken(); - if (!"{".equals(token)) { - throw new ApiParseException("expected '{' got " + token, tokenizer.getLine()); - } - while (true) { - token = tokenizer.requireToken(); - if ("}".equals(token)) { - break; - } else { - parseClass(state, tokenizer, token); - } - } - - state.finishPackage(); - } - - private static void parseClass(ParserState state, Tokenizer tokenizer, String token) - throws ApiParseException { - boolean pub = false; - boolean prot = false; - boolean pkgpriv = false; - boolean stat = false; - boolean fin = false; - boolean abs = false; - boolean dep = false; - boolean iface; - String name; - String qname; - - // even though we don't care about all those parameters, we keep this parsing logic - // to make sure we go through all the tokens. - - if ("public".equals(token)) { - pub = true; - token = tokenizer.requireToken(); - } else if ("protected".equals(token)) { - prot = true; - token = tokenizer.requireToken(); - } else { - pkgpriv = true; - } - if ("static".equals(token)) { - stat = true; - token = tokenizer.requireToken(); - } - if ("final".equals(token)) { - fin = true; - token = tokenizer.requireToken(); - } - if ("abstract".equals(token)) { - abs = true; - token = tokenizer.requireToken(); - } - if ("deprecated".equals(token)) { - dep = true; - token = tokenizer.requireToken(); - } - if ("class".equals(token)) { - iface = false; - token = tokenizer.requireToken(); - } else if ("interface".equals(token)) { - iface = true; - token = tokenizer.requireToken(); - } else { - throw new ApiParseException("missing class or interface. got: " + token, - tokenizer.getLine()); - } - assertIdent(tokenizer, token); - name = token; - token = tokenizer.requireToken(); - - state.addClass(name); - - // even though we don't care about all those parameters, we keep this parsing logic - // to make sure we go through all the tokens. - - - if ("extends".equals(token)) { - token = tokenizer.requireToken(); - assertIdent(tokenizer, token); - state.addSuperClass(token); - token = tokenizer.requireToken(); - } - - // Resolve superclass after done parsing - if ("implements".equals(token)) { - while (true) { - token = tokenizer.requireToken(); - if ("{".equals(token)) { - break; - } else { - if (!",".equals(token)) { - state.addInterface(token); - } - } - } - } - - if (!"{".equals(token)) { - throw new ApiParseException("expected {", tokenizer.getLine()); - } - - token = tokenizer.requireToken(); - while (true) { - if ("}".equals(token)) { - break; - } else if ("ctor".equals(token)) { - token = tokenizer.requireToken(); - parseConstructor(tokenizer, state, token); - } else if ("method".equals(token)) { - token = tokenizer.requireToken(); - parseMethod(tokenizer, state, token); - } else if ("field".equals(token)) { - token = tokenizer.requireToken(); - parseField(tokenizer, state, token, false); - } else if ("enum_constant".equals(token)) { - token = tokenizer.requireToken(); - parseField(tokenizer, state, token, true); - } else { - throw new ApiParseException("expected ctor, enum_constant, field or method", - tokenizer.getLine()); - } - token = tokenizer.requireToken(); - } - - state.finishClass(); - } - - private static void parseConstructor(Tokenizer tokenizer, ParserState state, String token) - throws ApiParseException { - boolean pub = false; - boolean prot = false; - boolean pkgpriv = false; - boolean dep = false; - String name; - - if ("public".equals(token)) { - pub = true; - token = tokenizer.requireToken(); - } else if ("protected".equals(token)) { - prot = true; - token = tokenizer.requireToken(); - } else { - pkgpriv = true; - } - if ("deprecated".equals(token)) { - dep = true; - token = tokenizer.requireToken(); - } - assertIdent(tokenizer, token); - name = token; - token = tokenizer.requireToken(); - if (!"(".equals(token)) { - throw new ApiParseException("expected (", tokenizer.getLine()); - } - - state.startNewConstructor(); - - token = tokenizer.requireToken(); - parseParameterList(tokenizer, state, token); - token = tokenizer.requireToken(); - if ("throws".equals(token)) { - token = parseThrows(tokenizer, state); - } - if (!";".equals(token)) { - throw new ApiParseException("expected ; found " + token, tokenizer.getLine()); - } - - state.finishMethod(); - } - - private static void parseMethod(Tokenizer tokenizer, ParserState state, String token) - throws ApiParseException { - boolean pub = false; - boolean prot = false; - boolean pkgpriv = false; - boolean stat = false; - boolean fin = false; - boolean abs = false; - boolean dep = false; - boolean syn = false; - String type; - String name; - String ext = null; - - if ("public".equals(token)) { - pub = true; - token = tokenizer.requireToken(); - } else if ("protected".equals(token)) { - prot = true; - token = tokenizer.requireToken(); - } else { - pkgpriv = true; - } - if ("static".equals(token)) { - stat = true; - token = tokenizer.requireToken(); - } - if ("final".equals(token)) { - fin = true; - token = tokenizer.requireToken(); - } - if ("abstract".equals(token)) { - abs = true; - token = tokenizer.requireToken(); - } - if ("deprecated".equals(token)) { - dep = true; - token = tokenizer.requireToken(); - } - if ("synchronized".equals(token)) { - syn = true; - token = tokenizer.requireToken(); - } - assertIdent(tokenizer, token); - type = token; - token = tokenizer.requireToken(); - assertIdent(tokenizer, token); - name = token; - - state.startNewMethod(name, type); - - token = tokenizer.requireToken(); - if (!"(".equals(token)) { - throw new ApiParseException("expected (", tokenizer.getLine()); - } - token = tokenizer.requireToken(); - parseParameterList(tokenizer, state, token); - token = tokenizer.requireToken(); - if ("throws".equals(token)) { - token = parseThrows(tokenizer, state); - } - if (!";".equals(token)) { - throw new ApiParseException("expected ; found " + token, tokenizer.getLine()); - } - - state.finishMethod(); - } - - private static void parseField(Tokenizer tokenizer, ParserState state, String token, boolean isEnum) - throws ApiParseException { - boolean pub = false; - boolean prot = false; - boolean pkgpriv = false; - boolean stat = false; - boolean fin = false; - boolean dep = false; - boolean trans = false; - boolean vol = false; - String type; - String name; - String val = null; - Object v; - - if ("public".equals(token)) { - pub = true; - token = tokenizer.requireToken(); - } else if ("protected".equals(token)) { - prot = true; - token = tokenizer.requireToken(); - } else { - pkgpriv = true; - } - if ("static".equals(token)) { - stat = true; - token = tokenizer.requireToken(); - } - if ("final".equals(token)) { - fin = true; - token = tokenizer.requireToken(); - } - if ("deprecated".equals(token)) { - dep = true; - token = tokenizer.requireToken(); - } - if ("transient".equals(token)) { - trans = true; - token = tokenizer.requireToken(); - } - if ("volatile".equals(token)) { - vol = true; - token = tokenizer.requireToken(); - } - assertIdent(tokenizer, token); - type = token; - token = tokenizer.requireToken(); - assertIdent(tokenizer, token); - name = token; - token = tokenizer.requireToken(); - if ("=".equals(token)) { - token = tokenizer.requireToken(false); - val = token; - token = tokenizer.requireToken(); - } - if (!";".equals(token)) { - throw new ApiParseException("expected ; found " + token, tokenizer.getLine()); - } - - if (isEnum) { - state.addField(name); - } else { - state.addField(name); - } - } - - private static void parseParameterList(Tokenizer tokenizer, ParserState state, - String token) throws ApiParseException { - while (true) { - if (")".equals(token)) { - return; - } - - String type = token; - String name = null; - token = tokenizer.requireToken(); - if (isIdent(token)) { - name = token; - token = tokenizer.requireToken(); - } - if (",".equals(token)) { - token = tokenizer.requireToken(); - } else if (")".equals(token)) { - } else { - throw new ApiParseException("expected , found " + token, tokenizer.getLine()); - } - state.addMethodParameter(type); -// method.addParameter(new ParameterInfo(name, type, Converter.obtainTypeFromString(type), -// type.endsWith("..."), tokenizer.pos())); - } - } - - private static String parseThrows(Tokenizer tokenizer, ParserState state) - throws ApiParseException { - String token = tokenizer.requireToken(); - boolean comma = true; - while (true) { - if (";".equals(token)) { - return token; - } else if (",".equals(token)) { - if (comma) { - throw new ApiParseException("Expected exception, got ','", tokenizer.getLine()); - } - comma = true; - } else { - if (!comma) { - throw new ApiParseException("Expected ',' or ';' got " + token, - tokenizer.getLine()); - } - comma = false; - } - token = tokenizer.requireToken(); - } - } - -// private static String qualifiedName(String pkg, String className, ClassInfo parent) { -// String parentQName = (parent != null) ? (parent.qualifiedName() + ".") : ""; -// return pkg + "." + parentQName + className; -// } - - private static boolean isIdent(String token) { - return isident(token.charAt(0)); - } - - private static void assertIdent(Tokenizer tokenizer, String token) throws ApiParseException { - if (!isident(token.charAt(0))) { - throw new ApiParseException("Expected identifier: " + token, tokenizer.getLine()); - } - } - - static class Tokenizer { - char[] mBuf; - - String mFilename; - - int mPos; - - int mLine = 1; - - Tokenizer(String filename, char[] buf) { - mFilename = filename; - mBuf = buf; - } - - public int getLine() { - return mLine; - } - - boolean eatWhitespace() { - boolean ate = false; - while (mPos < mBuf.length && isspace(mBuf[mPos])) { - if (mBuf[mPos] == '\n') { - mLine++; - } - mPos++; - ate = true; - } - return ate; - } - - boolean eatComment() { - if (mPos + 1 < mBuf.length) { - if (mBuf[mPos] == '/' && mBuf[mPos + 1] == '/') { - mPos += 2; - while (mPos < mBuf.length && !isnewline(mBuf[mPos])) { - mPos++; - } - return true; - } - } - return false; - } - - void eatWhitespaceAndComments() { - while (eatWhitespace() || eatComment()) { - } - } - - public String requireToken() throws ApiParseException { - return requireToken(true); - } - - public String requireToken(boolean parenIsSep) throws ApiParseException { - final String token = getToken(parenIsSep); - if (token != null) { - return token; - } else { - throw new ApiParseException("Unexpected end of file", mLine); - } - } - - public String getToken() throws ApiParseException { - return getToken(true); - } - - public String getToken(boolean parenIsSep) throws ApiParseException { - eatWhitespaceAndComments(); - if (mPos >= mBuf.length) { - return null; - } - final int line = mLine; - final char c = mBuf[mPos]; - final int start = mPos; - mPos++; - if (c == '"') { - final int STATE_BEGIN = 0; - final int STATE_ESCAPE = 1; - int state = STATE_BEGIN; - while (true) { - if (mPos >= mBuf.length) { - throw new ApiParseException("Unexpected end of file for \" starting at " - + line, mLine); - } - final char k = mBuf[mPos]; - if (k == '\n' || k == '\r') { - throw new ApiParseException( - "Unexpected newline for \" starting at " + line, mLine); - } - mPos++; - switch (state) { - case STATE_BEGIN: - switch (k) { - case '\\': - state = STATE_ESCAPE; - mPos++; - break; - case '"': - return new String(mBuf, start, mPos - start); - } - case STATE_ESCAPE: - state = STATE_BEGIN; - break; - } - } - } else if (issep(c, parenIsSep)) { - return "" + c; - } else { - int genericDepth = 0; - do { - while (mPos < mBuf.length && !isspace(mBuf[mPos]) - && !issep(mBuf[mPos], parenIsSep)) { - mPos++; - } - if (mPos < mBuf.length) { - if (mBuf[mPos] == '<') { - genericDepth++; - mPos++; - } else if (mBuf[mPos] == '>') { - genericDepth--; - mPos++; - } else if (genericDepth != 0) { - mPos++; - } - } - } while (mPos < mBuf.length - && ((!isspace(mBuf[mPos]) && !issep(mBuf[mPos], parenIsSep)) || genericDepth != 0)); - if (mPos >= mBuf.length) { - throw new ApiParseException( - "Unexpected end of file for \" starting at " + line, mLine); - } - return new String(mBuf, start, mPos - start); - } - } - } - - static boolean isspace(char c) { - return c == ' ' || c == '\t' || c == '\n' || c == '\r'; - } - - static boolean isnewline(char c) { - return c == '\n' || c == '\r'; - } - - static boolean issep(char c, boolean parenIsSep) { - if (parenIsSep) { - if (c == '(' || c == ')') { - return true; - } - } - return c == '{' || c == '}' || c == ',' || c == ';' || c == '<' || c == '>'; - } - - static boolean isident(char c) { - if (c == '"' || issep(c, true)) { - return false; - } - return true; - } -} diff --git a/apigenerator/src/com/android/apigenerator/ParserState.java b/apigenerator/src/com/android/apigenerator/ParserState.java deleted file mode 100644 index 7ffb57a..0000000 --- a/apigenerator/src/com/android/apigenerator/ParserState.java +++ /dev/null @@ -1,174 +0,0 @@ -/* - * Copyright (C) 2012 The Android Open Source Project - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ - -package com.android.apigenerator; - -import java.util.Map; - -/** - * Parser state used during parsing of the platform API files. - * - */ -class ParserState { - - private final int mApiLevel; - - private final Map mClasses; - - private String mCurrentPackage; - private ApiClass mCurrentClass; - - private String mMethodName; - private StringBuilder mMethodParams = new StringBuilder(); - private String mMethodReturnType; - - ParserState(Map classes, int apiLevel) { - mClasses = classes; - mApiLevel = apiLevel; - } - - Map getClasses() { - return mClasses; - } - - void addPackage(String packageName) { - mCurrentPackage = packageName; - } - - void addClass(String className) { - String fqcn = makeJavaClass(mCurrentPackage + "." + className); - mCurrentClass = addClass(fqcn, mApiLevel); - } - - void addSuperClass(String superClass) { - mCurrentClass.addSuperClass(makeJavaClass(superClass), mApiLevel); - } - - void addInterface(String interfaceClass) { - mCurrentClass.addInterface(makeJavaClass(interfaceClass), mApiLevel); - } - - void startNewConstructor() { - mMethodParams.setLength(0); - mMethodName = ""; - mMethodReturnType = "V"; - } - - void startNewMethod(String name, String returnType) { - mMethodParams.setLength(0); - mMethodName = name; - mMethodReturnType = parseType(returnType); - } - - void addMethodParameter(String parameter) { - mMethodParams.append(parseType(parameter)); - } - - void finishMethod() { - addMethod(mMethodName + "(" + mMethodParams.toString() + ")" + - (mMethodReturnType != null ? mMethodReturnType : "")); - } - - void addMethod(String methodSignature) { - mCurrentClass.addMethod(methodSignature, mApiLevel); - } - - void addField(String fieldName) { - mCurrentClass.addField(fieldName, mApiLevel); - } - - void finishClass() { - mCurrentClass = null; - } - - void finishPackage() { - finishClass(); - mCurrentPackage = null; - } - - void done() { - finishPackage(); - } - - private ApiClass addClass(String name, int apiLevel) { - ApiClass theClass = mClasses.get(name); - if (theClass == null) { - theClass = new ApiClass(name, apiLevel); - mClasses.put(name, theClass); - } - - return theClass; - } - - - private String makeJavaClass(String fqcn) { - final int length = fqcn.length(); - - StringBuilder sb = new StringBuilder(length); - - boolean isClass = Character.isUpperCase(fqcn.charAt(0)); - for (int i = 0 ; i < length ; i++) { - if (fqcn.charAt(i) == '.') { - if (isClass) { - sb.append('$'); - } else { - sb.append('/'); - } - - if (i < length -1 ) { - isClass = Character.isUpperCase(fqcn.charAt(i+1)); - } - } else { - if (fqcn.charAt(i) == '<') { - break; - } - - sb.append(fqcn.charAt(i)); - } - } - - return sb.toString(); - } - - private String parseType(String type) { - StringBuilder result = new StringBuilder(); - - if (type.endsWith("...")) { - result.append('['); - type = type.substring(0, type.length() - 3); - } - - while (type.endsWith("[]")) { - result.append('['); - type = type.substring(0, type.length() - 2); - } - - if ("byte".equals(type)) result.append('B'); - else if ("char".equals(type)) result.append('C'); - else if ("double".equals(type)) result.append('D'); - else if ("float".equals(type)) result.append('F'); - else if ("int".equals(type)) result.append('I'); - else if ("long".equals(type)) result.append('J'); - else if ("short".equals(type)) result.append('S'); - else if ("void".equals(type)) result.append('V'); - else if ("boolean".equals(type)) result.append('Z'); - else { - result.append('L').append(makeJavaClass(type)).append(';'); - } - - return result.toString(); - } -} diff --git a/apigenerator/src/com/android/apigenerator/XmlApiParser.java b/apigenerator/src/com/android/apigenerator/XmlApiParser.java deleted file mode 100644 index 840272c..0000000 --- a/apigenerator/src/com/android/apigenerator/XmlApiParser.java +++ /dev/null @@ -1,126 +0,0 @@ -/* - * Copyright (C) 2012 The Android Open Source Project - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ - -package com.android.apigenerator; - -import org.xml.sax.Attributes; -import org.xml.sax.SAXException; -import org.xml.sax.helpers.DefaultHandler; - -import java.util.Map; - -/** - * Parser for the old, XML-based format of platform API files. - */ -class XmlApiParser extends DefaultHandler { - - private final static String NODE_API = "api"; - private final static String NODE_PACKAGE = "package"; - private final static String NODE_CLASS = "class"; - private final static String NODE_INTERFACE = "interface"; - private final static String NODE_IMPLEMENTS = "implements"; - private final static String NODE_FIELD = "field"; - private final static String NODE_CONSTRUCTOR = "constructor"; - private final static String NODE_METHOD = "method"; - private final static String NODE_PARAMETER = "parameter"; - - private final static String ATTR_NAME = "name"; - private final static String ATTR_TYPE = "type"; - private final static String ATTR_RETURN = "return"; - private final static String ATTR_EXTENDS = "extends"; - - private final ParserState mParserState; - - XmlApiParser(Map map, int apiLevel) { - mParserState = new ParserState(map, apiLevel); - } - - @Override - public void startElement(String uri, String localName, String qName, Attributes attributes) - throws SAXException { - - if (localName == null || localName.length() == 0) { - localName = qName; - } - - try { - - if (NODE_API.equals(localName)) { - } else if (NODE_PACKAGE.equals(localName)) { - mParserState.addPackage(attributes.getValue(ATTR_NAME)); - - } else if (NODE_CLASS.equals(localName) || NODE_INTERFACE.equals(localName)) { - mParserState.addClass(attributes.getValue(ATTR_NAME)); - - String extendsAttr = attributes.getValue(ATTR_EXTENDS); - if (extendsAttr != null) { - mParserState.addSuperClass(extendsAttr); - } - - } else if (NODE_IMPLEMENTS.equals(localName)) { - mParserState.addInterface(attributes.getValue(ATTR_NAME)); - - } else if (NODE_FIELD.equals(localName)) { - mParserState.addField(attributes.getValue(ATTR_NAME)); - - } else if (NODE_CONSTRUCTOR.equals(localName)) { - parseConstructor(attributes); - - } else if (NODE_METHOD.equals(localName)) { - parseMethod(attributes); - - } else if (NODE_PARAMETER.equals(localName)) { - parseParameter(attributes); - } - - } finally { - super.startElement(uri, localName, qName, attributes); - } - } - - private void parseConstructor(Attributes attributes) { - mParserState.startNewConstructor(); - } - - private void parseMethod(Attributes attributes) { - mParserState.startNewMethod(attributes.getValue(ATTR_NAME), - attributes.getValue(ATTR_RETURN)); - } - - private void parseParameter(Attributes attributes) { - mParserState.addMethodParameter(attributes.getValue(ATTR_TYPE)); - } - - @Override - public void endElement(String uri, String localName, String qName) throws SAXException { - if (localName == null || localName.length() == 0) { - localName = qName; - } - - try { - - if (NODE_METHOD.equals(localName) || NODE_CONSTRUCTOR.equals(localName)) { - mParserState.finishMethod(); - - } else if (NODE_API.equals(localName)) { - mParserState.done(); - } - - } finally { - super.endElement(uri, localName, qName); - } - } -} \ No newline at end of file diff --git a/apigenerator/src/com/android/apigenerator/enumfix/AndroidJarReader.java b/apigenerator/src/com/android/apigenerator/enumfix/AndroidJarReader.java deleted file mode 100644 index 7669786..0000000 --- a/apigenerator/src/com/android/apigenerator/enumfix/AndroidJarReader.java +++ /dev/null @@ -1,132 +0,0 @@ -/* - * Copyright (C) 2012 The Android Open Source Project - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ - -package com.android.apigenerator.enumfix; - -import com.android.apigenerator.ApiClass; - -import org.objectweb.asm.ClassReader; -import org.objectweb.asm.tree.ClassNode; -import org.objectweb.asm.tree.FieldNode; - -import java.io.File; -import java.io.FileInputStream; -import java.io.FileNotFoundException; -import java.io.IOException; -import java.net.MalformedURLException; -import java.util.HashMap; -import java.util.List; -import java.util.Map; -import java.util.zip.ZipEntry; -import java.util.zip.ZipInputStream; - -/** - * This codes looks at all the android.jar in an SDK and use ASM to figure out when enums - * where introduced. This is a one time thing that creates the file - * /com/android/apichecker/generator/enums.xml which is then used to create the final API file. - * - */ -public class AndroidJarReader { - - // this the last API until we switched to a new API format that included enum values. - private final static int MAX_API = 13; - private static final byte[] BUFFER = new byte[65535]; - - private final String mSdkFolder; - - public AndroidJarReader(String sdkFolder) { - mSdkFolder = sdkFolder; - } - - public Map getEnumClasses() { - HashMap map = new HashMap(); - - // Get all the android.jar. They are in platforms-# - for (int apiLevel = 1 ; apiLevel <= MAX_API ; apiLevel++) { - try { - File jar = new File(mSdkFolder, "platforms/android-" + apiLevel + "/android.jar"); - if (jar.exists() == false) { - System.err.println("Missing android.jar for API level " + apiLevel); - continue; - } - - FileInputStream fis = new FileInputStream(jar); - ZipInputStream zis = new ZipInputStream(fis); - ZipEntry entry = zis.getNextEntry(); - while (entry != null) { - String name = entry.getName(); - - if (name.endsWith(".class")) { - - int index = 0; - do { - int size = zis.read(BUFFER, index, BUFFER.length - index); - if (size >= 0) { - index += size; - } else { - break; - } - } while (true); - - byte[] b = new byte[index]; - System.arraycopy(BUFFER, 0, b, 0, index); - - ClassReader reader = new ClassReader(b); - ClassNode classNode = new ClassNode(); - reader.accept(classNode, 0 /*flags*/); - - if (classNode != null && classNode.superName != null && - classNode.superName.equals("java/lang/Enum")) { - - ApiClass theClass = addClass(map, classNode.name, apiLevel); - theClass.addSuperClass("java/lang/Enum", apiLevel); - - List fields = classNode.fields; - for (Object f : fields) { - FieldNode fnode = (FieldNode) f; - if (fnode.desc.substring(1, fnode.desc.length() - 1).equals(classNode.name)) { - theClass.addField(fnode.name, apiLevel); - } - } - } - } - entry = zis.getNextEntry(); - } - } catch (MalformedURLException e) { - e.printStackTrace(); - } catch (FileNotFoundException e) { - e.printStackTrace(); - } catch (IOException e) { - e.printStackTrace(); - } finally { - - } - } - - return map; - } - - private ApiClass addClass(HashMap classes, String name, int apiLevel) { - ApiClass theClass = classes.get(name); - if (theClass == null) { - theClass = new ApiClass(name, apiLevel); - classes.put(name, theClass); - } - - return theClass; - } - -} diff --git a/apigenerator/src/com/android/apigenerator/enums.xml b/apigenerator/src/com/android/apigenerator/enums.xml deleted file mode 100644 index 54a2a21..0000000 --- a/apigenerator/src/com/android/apigenerator/enums.xml +++ /dev/null @@ -1,596 +0,0 @@ - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -- cgit v1.1