Path: blob/master/sourcetools/com.ibm.jpp.preprocessor/com/ibm/jpp/om/ConfigXMLHandler.java
6004 views
/*******************************************************************************1* Copyright (c) 1999, 2020 IBM Corp. and others2*3* This program and the accompanying materials are made available under4* the terms of the Eclipse Public License 2.0 which accompanies this5* distribution and is available at https://www.eclipse.org/legal/epl-2.0/6* or the Apache License, Version 2.0 which accompanies this distribution and7* is available at https://www.apache.org/licenses/LICENSE-2.0.8*9* This Source Code may also be made available under the following10* Secondary Licenses when the conditions for such availability set11* forth in the Eclipse Public License, v. 2.0 are satisfied: GNU12* General Public License, version 2 with the GNU Classpath13* Exception [1] and GNU General Public License, version 2 with the14* OpenJDK Assembly Exception [2].15*16* [1] https://www.gnu.org/software/classpath/license.html17* [2] http://openjdk.java.net/legal/assembly-exception.html18*19* SPDX-License-Identifier: EPL-2.0 OR Apache-2.0 OR GPL-2.0 WITH Classpath-exception-2.0 OR LicenseRef-GPL-2.0 WITH Assembly-exception20*******************************************************************************/21package com.ibm.jpp.om;2223import java.io.File;24import java.io.FileInputStream;25import java.io.FileNotFoundException;26import java.io.IOException;27import java.util.ArrayList;28import java.util.HashMap;29import java.util.Iterator;30import java.util.List;31import java.util.Map;32import java.util.StringTokenizer;3334import com.ibm.jpp.xml.IXMLDocumentHandler;35import com.ibm.jpp.xml.XMLException;36import com.ibm.jpp.xml.XMLParser;3738/**39* <b>HOW TO USE XML TO SETUP CONFIGURATION AND PREPROCESS JOBS </b>40* <p>41* XMl files are used two different ways in the preprocessor42*43* <ol>44* <li>The default set of configurations is loaded when the preprocessing45* begins, loading in a number of configurations/sets from default.xml which46* must be in the plugin dir (if using the plugin) or the same location as the47* preprocess code is located. (Use the command line argument "-xmldefault48* {xmlfilepath}" to define a different default filepath</li>49* <li>A second xml file can be defined using the command line argument (-xml50* {xmlfilepath} {basedirectory}). With this command, the given xml file is51* parsed for not only configuration setup, but relative source and output paths52* for the build. The only other command line arguments currently allowed in53* combination with -xml is -xmldefault. All build information54* must be found inside the xml. More than one configuration to be built can be55* defined in the xml and configurations missing source or output dirs will be56* ignored.</li>57* </ol>58*59* <p>For more details of the command structure see CommandLineBuilder.java60* <p>61* <b>FUNCTIONALITY IN DEFAULT XML FILES FOR LOADING CONFIGURATIONS </b>62* <p>63* See the default.xml or the plugin's plugin.xml for examples64* <ol>65* <li>"set" and "configuration" elements must define the attribute label.66* </li>67* <li>Only configurations can have source/output elements</li>68* <li>A non-unique set/config label may result in dependency problems (-xml69* will override -xmldefault, but try to not have anything dependent on that70* set/config)</li>71* <li>flags attribute used to add individual flags, dependencies attr used to72* add all of the flags in that set/config (therefore the dependency set/config73* must be defined in -xml or -defaultxml</li>74* <li>Precede the flag/dependency name with '-' to remove, rather than add75* that flag/dependency from the final flagset, trying it remove flags that76* don't exist in the flagset will not cause an error.</li>77* <li>Options that apply to the entire set/config are added as a separate78* element called parameter, with attributes name and value.</li>79* <li>Other elements, such as extension point will be ignored</li>80* <li>Use the output path attribute of configuration to set the output path,81* note that this path will be combined with the {basedir} arg</li>82* <li>For each source, create a separate source element, with attribute path83* to define the source location. Add "type=simplecopy" to do a copy instead of84* a preprocess, add an element called parameter with attributes name and value85* to add an option to a specific source dir</li>86* </ol>87*88* <pre>89* EX:90* <set91* label="xtr"92* flags="xJava,xIO,xThread,xNet,Deprecated,MAX13">93* </set>94*95* EX:96* <configuration97* label="XTREME"98* outputpath="pConfig XTREME/src"99* dependencies="xtr">100* <source path="src">101* <parameter name="macro:define" value="com.ibm.oti.vm.library.version=29;com.ibm.oti.jcl.build=plugin2"/>102* </source>103* <parameter name="jxerules:outputdir" value="com/ibm/oti/util"/>104* </configuration>105* </pre>106*/107public class ConfigXMLHandler implements IXMLDocumentHandler {108109private static final int MAJOR_VERSION = 3;110111private final List<ConfigObject> configObjects;112private final List<ConfigObject> requiredObjects;113private ConfigObject currentConfig;114private final FileInputStream XMLInput;115116private int configVersion;117private String baseDir;118private String srcRoot;119private Src currentSource;120private boolean inSource = false;121private boolean inGlobals = false;122private boolean inAutomatedTests = false;123private String testsProject = "";124private final List<ClassPathEntry> testsClassPaths;125private final List<Src> testsSources;126private final List<ClassPathEntry> classPaths;127private final Map<String, String> defaultTestsResources;128/* [PR 119756] config project prefix and postfix support in jpp_configuration.xml file */129private String outputPathPrefix = "";130private String outputPathSuffix = "";131private String testsOutputPathPrefix = "";132private String testsOutputPathSuffix = " Tests";133private String bootTestsOutputPathPrefix = "";134private String bootTestsOutputPathSuffix = " Tests BootPath";135136/**137* Constructs a ConfigXMLHandler...138*139* @param filename the file to be parsed140*141* @throws FileNotFoundException if the file cannot be located142*/143public ConfigXMLHandler(String filename) throws FileNotFoundException {144this.XMLInput = new FileInputStream(filename);145srcRoot = "";146configObjects = new ArrayList<>();147requiredObjects = new ArrayList<>();148testsClassPaths = new ArrayList<>();149classPaths = new ArrayList<>();150testsSources = new ArrayList<>();151defaultTestsResources = new HashMap<>();152}153154public int getConfigVersion() {155return configVersion;156}157158/**159* @param baseDir the base directory160*/161public void setBaseDir(String baseDir) {162this.baseDir = baseDir;163}164165/**166* @param srcRoot the source root path167*/168public void setSourceRoot(String srcRoot) {169this.srcRoot = srcRoot;170}171172/**173* Returns only the configurations from the required XMLs. This is the opposite of174* {@link #getLocalConfigs()}.175*176* @return only the configurations from required XMLs177*/178public List<ConfigObject> getRequiredConfigs() {179return requiredObjects;180}181182/**183* Returns only the configurations defined in the parent XML (and not in184* the requires XMLs).185*186* @return only the configurations defined in the parent XML187*/188public List<ConfigObject> getLocalConfigs() {189return configObjects;190}191192/**193* Returns all of the configurations. This combines local and requires configs.194*195* @return all of the configurations196*197* @see #getLocalConfigs()198* @see #getRequiredConfigs()199*/200public List<ConfigObject> getAllConfigs() {201List<ConfigObject> temp = new ArrayList<>(requiredObjects);202temp.addAll(configObjects);203return temp;204}205206/**207* Returns the name of the automated tests project.208*209* @return the name of the automated tests project210*/211public String getTestsProject() {212return testsProject;213}214215/**216* Returns outputPathPrefix. Default value is "".217*218* @return outputPathPrefix219*/220/* [PR 119756] config project prefix and postfix support in jpp_configuration.xml file */221public String getOutputPathPrefix() {222return outputPathPrefix;223}224225/**226* Returns outputPathSuffix. Default value is "".227*228* @return outputPathSuffix229*/230/* [PR 119756] config project prefix and postfix support in jpp_configuration.xml file */231public String getOutputPathSuffix() {232return outputPathSuffix;233}234235/**236* Returns testsOutputPathPrefix. Default value is "".237*238* @return testsOutputPathPrefix239*/240/* [PR 119756] config project prefix and postfix support in jpp_configuration.xml file */241public String getTestsOutputPathPrefix() {242return testsOutputPathPrefix;243}244245/**246* Returns testsOutputPathSuffix. Default value is "".247*248* @return testsOutputPathSuffix249*/250/* [PR 119756] config project prefix and postfix support in jpp_configuration.xml file */251public String getTestsOutputPathSuffix() {252return testsOutputPathSuffix;253}254255/**256* Returns bootTestsOutputPathPrefix. Default value is "".257*258* @return bootTestsOutputPathPrefix259*/260/* [PR 119756] config project prefix and postfix support in jpp_configuration.xml file */261public String getBootTestsOutputPathPrefix() {262return bootTestsOutputPathPrefix;263}264265/**266* Returns bootTestsOutputPathSuffix. Default value is "".267*268* @return bootTestsOutputPathSuffix269*/270/* [PR 119756] config project prefix and postfix support in jpp_configuration.xml file */271public String getBootTestsOutputPathSuffix() {272return bootTestsOutputPathSuffix;273}274275/**276* Returns the automated tests classpath entries.277*278* @return the automated tests classpath entries279*/280public List<ClassPathEntry> getTestsClassPaths() {281return testsClassPaths;282}283284/**285* Returns the automated tests source entries.286*287* @return the automated tests source entries288*/289public List<Src> getTestsSources() {290return testsSources;291}292293/**294* Parses the configuration XML file.295*296* @throws XMLException if there are errors in the XML file297*/298public void parseInput() throws XMLException {299System.out.println("Reading preprocess instructions from xml...");300new XMLParser().parse(XMLInput, this);301}302303/**304* Insures that the base directory is not null.305*/306@Override307public void xmlStartDocument() {308if (baseDir == null) {309baseDir = "";310}311}312313/**314* Resolves any configuration dependencies of the parsed configuration. One315* configuration may be dependant upon other configurations meaning that and316* preprocessor flags held by the by the depended upon configurations are317* inherited.318*/319@Override320public void xmlEndDocument() {321try {322XMLInput.close();323} catch (IOException e) {324System.err.println("Could not close ConfigXMLHandler InputStream");325e.printStackTrace();326}327}328329/**330* A series of if statements to identify the significance of the parsed331* element and act accordingly.332*333* @param elementName the XML element name334* @param attributes the element's attributes335*336* @throws XMLException if there are errors in the XML file337*/338@Override339public void xmlStartElement(String elementName, Map<String, String> attributes) throws XMLException {340if (elementName.equals("configurationreg")) {341String version = attributes.get("version");342if (version == null) {343throw new XMLException("No JPP Configuration XML version defined");344} else {345int majorVersion = Integer.parseInt(version.substring(0, 1));346if (majorVersion > MAJOR_VERSION) {347throw new XMLException("Perprocessor can't handle JPP Configuration XML v" + majorVersion + ".x");348}349configVersion = majorVersion;350}351}352353if (elementName.equals("automatedtests")) {354if (attributes.get("project") != null) {355inAutomatedTests = true;356testsProject = attributes.get("project");357}358} else if (elementName.equals("require") && attributes.get("name") != null) {359try {360ConfigurationRegistry requiredRegistry = MetaRegistry.getRegistry(baseDir, attributes.get("name"), ConfigurationRegistry.DEFAULT_XML);361requiredObjects.addAll(requiredRegistry.getConfigurationsAsCollection());362} catch (FileNotFoundException e) {363System.out.println("Could not find the XML configuration file: " + ConfigurationRegistry.DEFAULT_XML + "\n");364}365} else if (elementName.equals("set")) {366// Sets are groups of flags that are typically not used in a367// preprocessing job but insted act as dependencies for defined368// configurations.369370currentConfig = new ConfigObject(attributes.get("label"), false);371if (attributes.get("flags") != null) {372StringTokenizer t = new StringTokenizer(attributes.get("flags"), ",");373374while (t.hasMoreTokens()) {375String currentString = t.nextToken().trim();376/*377* You can specify that you wish a flag removed from the configuration or set with a preceding '-' character.378* For example if the final flag set (after dependency resolution) was: A, B, C and -A the '-A' would signify379* the removal of flag 'A' and only flags B and C would remain.380*/381if (currentString.charAt(0) == '-') {382currentConfig.addFlagToRemoveFlagSet(currentString.substring(1));383} else if (currentString.charAt(0) == '*') {384/*385* The '*' symbol prefixing any added flags is used when a required flag is needed. If a required flag is386* added to a configuration then for a java file to be accepted in preprocessing it must include at least387* the required flag and possibly others.388*/389currentConfig.addRequiredIncludeFlag(currentString.substring(1));390currentConfig.addFlag(currentString.substring(1));391} else {392currentConfig.addFlag(currentString);393}394}395}396397/*398* Dependencies are other configurations or that can be incorporated into the current configuration (or set). For399* example if there existed a configuration called 'depX' with the flags Y and Z and the configuration 'configA'400* included flag A and the dependency on 'depX' then the final flag set of configA would be A, Y and Z.401*/402if (attributes.get("dependencies") != null) {403StringTokenizer t = new StringTokenizer(attributes.get("dependencies"), ",");404405while (t.hasMoreTokens()) {406String currentString = t.nextToken().trim();407408// Dependencies can be specified for removal just as flags409if (currentString.charAt(0) == '-') {410currentConfig.removeFlagDependency(currentString.substring(1));411} else {412currentConfig.addFlagDependency(currentString);413}414}415}416417configObjects.add(currentConfig);418} else if (elementName.equals("configuration")) {419if (attributes.get("name") != null) {420currentConfig = new ConfigObject(attributes.get("name"), baseDir, true);421} else {422currentConfig = new ConfigObject(attributes.get("label"), baseDir, true);423if (attributes.get("flags") != null) {424StringTokenizer t = new StringTokenizer(attributes.get("flags"), ",");425while (t.hasMoreTokens()) {426String currentString = t.nextToken().trim();427if (currentString.charAt(0) == '-') {428currentConfig.addFlagToRemoveFlagSet(currentString.substring(1));429} else if (currentString.charAt(0) == '*') {430currentConfig.addRequiredIncludeFlag(currentString.substring(1));431currentConfig.addFlag(currentString.substring(1));432} else {433currentConfig.addFlag(currentString);434}435}436}437if (attributes.get("dependencies") != null) {438StringTokenizer t = new StringTokenizer(attributes.get("dependencies"), ",");439while (t.hasMoreTokens()) {440String currentString = t.nextToken().trim();441if (currentString.charAt(0) == '-') {442currentConfig.removeFlagDependency(currentString.substring(1));443} else {444currentConfig.addFlagDependency(currentString);445}446}447}448/* [PR 119756] config project prefix and postfix support in jpp_configuration.xml file */449// add global suffixes and prefixes450currentConfig.setOutputPathPrefix(outputPathPrefix);451currentConfig.setOutputPathSuffix(outputPathSuffix);452currentConfig.setTestsOutputPathPrefix(testsOutputPathPrefix);453currentConfig.setTestsOutputPathSuffix(testsOutputPathSuffix);454currentConfig.setBootTestsOutputPathPrefix(bootTestsOutputPathPrefix);455currentConfig.setBootTestsOutputPathSuffix(bootTestsOutputPathSuffix);456currentConfig.setDefaultTestsResourcesPrefixes(defaultTestsResources);457}458459if (attributes.get("jdkcompliance") != null) {460currentConfig.setJDKCompliance(attributes.get("jdkcompliance"));461}462463if (attributes.get("outputpath") != null) {464currentConfig.setOutputPathKeyword(attributes.get("outputpath"));465currentConfig.setOutputPath(attributes.get("outputpath"));466currentConfig.setTestsOutputPaths();467currentConfig.setBootTestsOutputPaths();468}469470if (attributes.get("jppmetadata") != null) {471currentConfig.enableMetadata(attributes.get("jppmetadata").equals("true"));472}473474configObjects.add(currentConfig);475} else if (elementName.equals("source")) {476String path = attributes.get("path");477if (!inAutomatedTests) {478path = (path.startsWith("/")) ? path : srcRoot + path;479}480481if (attributes.get("type") != null && attributes.get("outputpath") != null) {482currentSource = new Src(path, attributes.get("outputpath"));483} else if (attributes.get("type") != null) {484currentSource = new Src(path, attributes.get("type").equals("simplecopy"));485} else {486currentSource = new Src(path);487}488489inSource = true;490} else if (elementName.equals("parameter")) {491if (inSource) {492currentSource.addOption(attributes.get("name"), attributes.get("value"));493/* [PR 119756] config project prefix and postfix support in jpp_configuration.xml file */494} else if (inGlobals) {495if (attributes.get("name").equals("outputPathPrefix")) {496outputPathPrefix = attributes.get("value");497} else if (attributes.get("name").equals("outputPathSuffix")) {498outputPathSuffix = attributes.get("value");499} else if (attributes.get("name").equals("testsOutputPathPrefix")) {500testsOutputPathPrefix = attributes.get("value");501} else if (attributes.get("name").equals("testsOutputPathSuffix")) {502testsOutputPathSuffix = attributes.get("value");503} else if (attributes.get("name").equals("bootTestsOutputPathPrefix")) {504bootTestsOutputPathPrefix = attributes.get("value");505} else if (attributes.get("name").equals("bootTestsOutputPathSuffix")) {506bootTestsOutputPathSuffix = attributes.get("value");507} else {508System.out.println(attributes.get("name") + " is not valid global variable.");509}510} else {511/* [PR 119756] config project prefix and postfix support in jpp_configuration.xml file */512// if prefixes and suffixes are defined in configuration, then overwrite global values.513if (attributes.get("name").equals(outputPathPrefix)) {514currentConfig.setOutputPathPrefix(outputPathPrefix);515} else if (attributes.get("name").equals(outputPathSuffix)) {516currentConfig.setOutputPathSuffix(outputPathSuffix);517} else if (attributes.get("name").equals(testsOutputPathPrefix)) {518currentConfig.setTestsOutputPathPrefix(testsOutputPathPrefix);519} else if (attributes.get("name").equals(testsOutputPathSuffix)) {520currentConfig.setTestsOutputPathSuffix(testsOutputPathSuffix);521} else if (attributes.get("name").equals(bootTestsOutputPathPrefix)) {522currentConfig.setBootTestsOutputPathPrefix(bootTestsOutputPathPrefix);523} else if (attributes.get("name").equals(bootTestsOutputPathSuffix)) {524currentConfig.setBootTestsOutputPathSuffix(bootTestsOutputPathSuffix);525} else {526currentConfig.addOption(attributes.get("name"), attributes.get("value"));527}528}529/* [PR 118829] Desing 894: Core.JCL : Support for compiler options in preprocessor plugin */530} else if (elementName.equals("coption")) {531currentConfig.addCompilerOption(attributes.get("name"), attributes.get("value"));532} else if (elementName.equals("classpathentry")) {533boolean exported = Boolean.parseBoolean(attributes.get("exported"));534/* [PR 120359] New classpath entry is needed for configurations */535String kind = attributes.get("kind");536if (kind != null) {537String path = attributes.get("path");538String sourcepath = attributes.get("sourcepath");539ClassPathEntry entry;540if (sourcepath != null) {541entry = new ClassPathEntry(path, kind, sourcepath, exported);542} else {543entry = new ClassPathEntry(path, kind, exported);544}545classPaths.add(entry);546} else {547String registry = "this";548if (attributes.containsKey("registry")) {549registry = attributes.get("registry");550}551String configName = attributes.get("configName");552String project = attributes.get("project");553ConfigObject foundConfig = null;554String path = null;555if (registry.equals("this") || registry.equals(srcRoot.substring(0, srcRoot.length() - 1))) {556for (ConfigObject config : configObjects) {557if (config.isConfiguration() && config.getName().equals(configName)) {558foundConfig = config;559break;560}561}562} else {563try {564// the following two lines are risky in some cases,565// for example, if current registry needs an config from another registry and another registry needs a566// config from current registry,567// this may cause a loop and you might see overflow exception on the window,568// This part might be removed or can have better solution,569// Be aware that we dont point to another registry for now, and really low possibility in the future to570// point a registry from an other registry.571ConfigurationRegistry configReg = MetaRegistry.getRegistry(baseDir, registry, ConfigurationRegistry.DEFAULT_XML);572foundConfig = configReg.getConfiguration(configName);573} catch (FileNotFoundException e) {574if (currentConfig != null) {575currentConfig.setXMLParserError("XML PARSER ERROR : Config : " + currentConfig.getName()576+ ", Element : \"classpathentry\", Reason : registry could not be found : " + baseDir577+ (registry.equals("this") ? srcRoot : (registry + File.separator)) + ConfigurationRegistry.DEFAULT_XML + "\n");578}579}580}581if (foundConfig != null) {582if (project.equals("config")) {583path = "/" + foundConfig.getOutputPathPrefix() + foundConfig.getOutputPathKeyword() + foundConfig.getOutputPathSuffix();584} else if (project.equals("tests")) {585path = "/" + foundConfig.getTestsOutputPathPrefix() + foundConfig.getOutputPathKeyword() + foundConfig.getTestsOutputPathSuffix();586} else if (project.equals("boottests")) {587path = "/" + foundConfig.getBootTestsOutputPathPrefix() + foundConfig.getOutputPathKeyword() + foundConfig.getBootTestsOutputPathSuffix();588} else {589if (currentConfig != null) {590currentConfig.setXMLParserError("XML PARSER ERROR : Attribute \"project\" can have the value \"config\", \"tests\" or \"boottests\"\n");591}592}593classPaths.add(new ClassPathEntry(path, "src", exported));594} else {595if (currentConfig != null) {596currentConfig.setXMLParserError("XML PARSER ERROR : Config : " + currentConfig.getName()597+ ", Element : \"classpathentry\", Reason : pointed config in classpathentry element could not be found. Pointed Config Name : " + configName598+ "\n");599}600}601}602} else if (elementName.equals("dependjob")) {603String dependJobName = attributes.get("config");604String dependJobRegistry = attributes.get("registry");605ConfigurationRegistry dependRegistry = null;606607try {608dependRegistry = MetaRegistry.getRegistry(baseDir, dependJobRegistry, ConfigurationRegistry.DEFAULT_XML);609} catch (FileNotFoundException e) {610System.out.println("Could not find the XML configuration file: /" + dependJobRegistry + "/" + ConfigurationRegistry.DEFAULT_XML + "\n");611} finally {612ConfigObject dependJob = (dependRegistry != null) ? dependRegistry.getConfiguration(dependJobName) : null;613currentConfig.addDependJob(dependJobRegistry + " - " + dependJobName, dependJob);614}615} else if (elementName.equals("globalParameters")) {616inGlobals = true;617/* [PR 121295] Support for resources' prefixes and directories */618} else if (elementName.equals("testsResources")) {619if (attributes.containsKey("defaults")) {620String defaults = attributes.get("defaults");621if (defaults.equals("true") && !inAutomatedTests) {622currentConfig.setUseTestResourcesDefaults(true);623}624} else {625String dir = attributes.get("directory");626String prefix = attributes.get("prefix");627628if (inAutomatedTests) {629String dirSlash = dir + "/";630631for (Iterator<String> iter = defaultTestsResources.keySet().iterator(); iter.hasNext();) {632String nextDir = iter.next();633634if (nextDir.startsWith(dirSlash) || nextDir.equals(dir)) {635iter.remove();636}637}638defaultTestsResources.put(dir, prefix);639} else {640currentConfig.setTestsResourcesPrefix(dir, prefix);641}642}643}644}645646/**647* Defines what action need be taken at the end of the certain elements.648*649* @param elementName the XML element name650*/651@Override652public void xmlEndElement(String elementName) {653if (elementName.equals("automatedtests")) {654testsClassPaths.addAll(classPaths);655classPaths.clear();656inAutomatedTests = false;657} else if (elementName.equals("configuration") || elementName.equals("set")) {658for (ClassPathEntry classPath : classPaths) {659currentConfig.addClassPathEntry(classPath);660}661classPaths.clear();662currentConfig = null;663} else if (elementName.equals("source")) {664if (inAutomatedTests) {665testsSources.add(currentSource);666} else {667currentConfig.addSource(currentSource);668}669currentSource = null;670inSource = false;671/* [PR 119756] config project prefix and postfix support in jpp_configuration.xml file */672} else if (elementName.equals("globalParameters")) {673inGlobals = false;674}675}676677/**678* Unused section of the interface679*680* @param chars681*/682@Override683public void xmlCharacters(String chars) {684// System.out.println("xmlChars: " + chars);685}686687}688689690