1 /* 2 * Copyright (C) 2018 The Android Open Source Project 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 17 package com.android.xsdc; 18 19 import java.io.File; 20 import java.io.FileInputStream; 21 import java.io.IOException; 22 import java.io.PrintWriter; 23 import java.nio.file.Paths; 24 import java.util.ArrayList; 25 import java.util.List; 26 import java.util.stream.Collectors; 27 28 import static java.lang.System.exit; 29 30 import com.android.xsdc.java.JavaCodeGenerator; 31 import com.android.xsdc.cpp.CppCodeGenerator; 32 33 import org.apache.commons.cli.CommandLine; 34 import org.apache.commons.cli.CommandLineParser; 35 import org.apache.commons.cli.GnuParser; 36 import org.apache.commons.cli.HelpFormatter; 37 import org.apache.commons.cli.Option; 38 import org.apache.commons.cli.OptionBuilder; 39 import org.apache.commons.cli.OptionGroup; 40 import org.apache.commons.cli.Options; 41 import org.apache.commons.cli.ParseException; 42 43 import javax.xml.parsers.SAXParser; 44 import javax.xml.parsers.SAXParserFactory; 45 46 public class Main { main(String[] args)47 public static void main(String[] args) throws Exception { 48 Options options = new Options(); 49 options.addOption(OptionBuilder 50 .withLongOpt("package") 51 .hasArgs(1) 52 .withDescription("Package name of the generated java file. " + 53 "file name of generated cpp file and header") 54 .create("p")); 55 options.addOption(OptionBuilder 56 .withLongOpt("outDir") 57 .hasArgs(1) 58 .withDescription("Out Directory") 59 .create("o")); 60 options.addOption(OptionBuilder 61 .withLongOpt("java") 62 .hasArgs(0) 63 .withDescription("Generate Java code.") 64 .create("j")); 65 options.addOption(OptionBuilder 66 .withLongOpt("cpp") 67 .hasArgs(0) 68 .withDescription("Generate Cpp code.") 69 .create("c")); 70 options.addOption(OptionBuilder 71 .withLongOpt("writer") 72 .hasArgs(0) 73 .withDescription("Generate Writer code.") 74 .create("w")); 75 options.addOption(OptionBuilder 76 .withLongOpt("nullability") 77 .hasArgs(0) 78 .withDescription("Add @NonNull or @Nullable annotation to generated java code.") 79 .create("n")); 80 options.addOption(OptionBuilder 81 .withLongOpt("genHas") 82 .hasArgs(0) 83 .withDescription("Generate public hasX() method") 84 .create("g")); 85 options.addOption(OptionBuilder 86 .withLongOpt("booleanGetter") 87 .hasArgs(0) 88 .withDescription("Generate isX() for boolean element or attribute.") 89 .create("b")); 90 options.addOption(OptionBuilder 91 .withLongOpt("tinyxml") 92 .hasArgs(0) 93 .withDescription("Generate code that uses libtinyxml2 instead of libxml2." 94 + " Smaller binaries, but does not support XInclude substitution, " 95 + " or ENTITY_REFs.") 96 .create("t")); 97 Option genEnumsOnly = OptionBuilder 98 .withLongOpt("genEnumsOnly") 99 .hasArgs(0) 100 .withDescription("Only generate enum converters in Cpp code.") 101 .create("e"); 102 options.addOption(genEnumsOnly); 103 Option genParserOnly = OptionBuilder 104 .withLongOpt("genParserOnly") 105 .hasArgs(0) 106 .withDescription("Only generate XML parser in Cpp code.") 107 .create("x"); 108 options.addOption(genParserOnly); 109 Option genDepFile = OptionBuilder 110 .withLongOpt("depfile") 111 .hasArgs(1) 112 .withDescription("Generate depfile for ninja.") 113 .create("d"); 114 options.addOption(genDepFile); 115 options.addOption(OptionBuilder 116 .withLongOpt("root") 117 .hasArgs(1) 118 .withDescription("Root element.") 119 .create("r")); 120 121 // "Only generate enums" and "Only generate parser" options are mutually exclusive. 122 OptionGroup genOnlyGroup = new OptionGroup(); 123 genOnlyGroup.setRequired(false); 124 genOnlyGroup.addOption(genEnumsOnly); 125 genOnlyGroup.addOption(genParserOnly); 126 options.addOptionGroup(genOnlyGroup); 127 128 CommandLineParser CommandParser = new GnuParser(); 129 CommandLine cmd; 130 131 try { 132 cmd = CommandParser.parse(options, args); 133 } catch (ParseException e) { 134 System.err.println(e.getMessage()); 135 help(options); 136 return; 137 } 138 139 String packageName = cmd.getOptionValue('p', null); 140 String outDir = cmd.getOptionValue('o', null); 141 boolean writer = cmd.hasOption('w'); 142 boolean nullability = cmd.hasOption('n'); 143 boolean genHas = cmd.hasOption('g'); 144 boolean enumsOnly = cmd.hasOption('e'); 145 boolean parserOnly = cmd.hasOption('x'); 146 boolean booleanGetter = cmd.hasOption('b'); 147 boolean useTinyXml = cmd.hasOption('t'); 148 String depFile = cmd.getOptionValue('d', null); 149 String[] rootElements = cmd.getOptionValues('r'); 150 151 if (cmd.getArgs().length != 1 || packageName == null) { 152 System.err.println("Error: no xsd files or package name"); 153 help(options); 154 } 155 156 if (outDir == null) { 157 outDir = "."; 158 } 159 160 String xsdFile = cmd.getArgs()[0]; 161 List<String> included = new ArrayList<>(); 162 included.add(xsdFile); 163 XmlSchema xmlSchema = parse(xsdFile, included); 164 165 // When -r (root element) is specified, then validate if it's defined in schema. 166 if (rootElements != null) { 167 for (String rootElement : rootElements) { 168 if (!xmlSchema.getElementMap().containsKey(rootElement)) { 169 System.err.println("Invalid root element(-r): " + rootElement); 170 System.exit(1); 171 } 172 } 173 } else { 174 // Raise an error when -r/--root is not specified but there's more than one 175 // candidate root elements (top-level elements). 176 if (xmlSchema.getElementMap().size() > 1) { 177 System.err.println("Missing -r/--root option: please specify the names of root " 178 + "elements. In Android.bp, use 'root_elements' property to set root " 179 + "elements. Possible root elements are: " 180 + xmlSchema.getElementMap().keySet().stream().map(s -> "\"" + s + "\"") 181 .collect(Collectors.joining(", "))); 182 System.exit(1); 183 } 184 } 185 186 if (cmd.hasOption('j')) { 187 File packageDir = new File(Paths.get(outDir, packageName.replace(".", "/")).toString()); 188 packageDir.mkdirs(); 189 FileSystem fs = new FileSystem(packageDir); 190 JavaCodeGenerator javaCodeGenerator = 191 new JavaCodeGenerator(xmlSchema, packageName, writer, nullability, genHas, 192 booleanGetter, rootElements); 193 javaCodeGenerator.print(fs); 194 } else if (cmd.hasOption('c')) { 195 File includeDir = new File(Paths.get(outDir, "include").toString()); 196 includeDir.mkdirs(); 197 FileSystem fs = new FileSystem(new File(outDir)); 198 int generators = enumsOnly ? CppCodeGenerator.GENERATE_ENUMS : 199 (parserOnly ? CppCodeGenerator.GENERATE_PARSER : 200 CppCodeGenerator.GENERATE_ENUMS | CppCodeGenerator.GENERATE_PARSER); 201 CppCodeGenerator cppCodeGenerator = 202 new CppCodeGenerator(xmlSchema, packageName, writer, generators, 203 booleanGetter, useTinyXml, rootElements); 204 cppCodeGenerator.print(fs); 205 } 206 207 if (depFile != null) { 208 writeDepFile(depFile, included); 209 } 210 } 211 parse(String xsdFile, List<String> included)212 private static XmlSchema parse(String xsdFile, List<String> included) throws Exception { 213 XmlSchema xmlSchema; 214 try (FileInputStream in = new FileInputStream(xsdFile)) { 215 SAXParserFactory factory = SAXParserFactory.newInstance(); 216 factory.setNamespaceAware(true); 217 SAXParser parser = factory.newSAXParser(); 218 XsdHandler xsdHandler = new XsdHandler(); 219 parser.parse(in, xsdHandler); 220 xmlSchema = xsdHandler.getSchema(); 221 } 222 for (String file : xmlSchema.getIncludeList()) { 223 String filePath = Paths.get(xsdFile).resolveSibling(file).toString(); 224 included.add(filePath); 225 XmlSchema temp = parse(filePath, included); 226 xmlSchema.include(temp); 227 } 228 return xmlSchema; 229 } 230 writeDepFile(String depFile, List<String> files)231 private static void writeDepFile(String depFile, List<String> files) 232 throws IOException { 233 try (PrintWriter out = new PrintWriter(new File(depFile))) { 234 out.println(String.format(" %s", String.join(" \\\n ", files))); 235 } 236 } 237 help(Options options)238 private static void help(Options options) { 239 new HelpFormatter().printHelp( 240 "xsdc path/to/xsd_file.xsd","", options, null, true); 241 System.exit(1); 242 } 243 } 244