Path: blob/master/sourcetools/com.ibm.jpp.preprocessor/com/ibm/jpp/om/Builder.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.IOException;25import java.io.OutputStream;26import java.util.ArrayList;27import java.util.Calendar;28import java.util.Date;29import java.util.GregorianCalendar;30import java.util.HashMap;31import java.util.List;32import java.util.Map;33import java.util.Properties;3435/**36* J9 JCL Perprocessor builder. The builder is responsible for all the tasks related to preprocessing37* a group of files. This includes managing the extensions, notifying others, verifying preprocess options,38* handling preprocessor warnings, and actually preprocessing the sources.39*/40public class Builder {4142public static boolean isForced(Map<?, ?> options) {43return Boolean.TRUE.equals(options.get("force"));44}4546private Properties options = new Properties();47private BuilderExtension[] extensions = new BuilderExtension[0];4849private Logger logger = new NullLogger();50private ConfigurationRegistry registry;51private ConfigObject configObject = null;52private boolean isIncremental = false;53private boolean enabledMetadata = false;5455private File sourceDir = null;5657/**58* The value is a String[] containing the relative paths of all of the build59* files for a given sourceDir.60*/61private final Map<File, String[]> buildFilesBySourceDir = new HashMap<>();62/*[PR 118220] Incremental builder is not called when file is deleted in base library*/63private final Map<File, List<String>> deleteFilesBySourceDir = new HashMap<>();64private final Map<File, List<String>> buildResourcesBySourceDir = new HashMap<>();6566private int buildFileCount = 0;67private int deleteFileCount = 0;68private int builtFileCount = 0;69private int buildResourcesCount = 0;70private File outputDir = null;71private boolean verdict = false;72private boolean includeIfUnsure = false;73/*[PR 117967] idea 491: Automatically create the jars required for test bootpath*/74private boolean isTestsBootPath = false;75private boolean noWarnIncludeIf = false;76private boolean noWarnInvalidFlags = false;77private boolean multipleSources = false;78private boolean updateAllCopyrights = false;7980/**81* J9 JCL Preprocessor builder constructor. Initializes the needed extensions.82*/83public Builder() {84addExtension(new ExternalMessagesExtension());85addExtension(new MacroExtension());86addExtension(new JxeRulesExtension());87addExtension(new EclipseMetadataExtension());88addExtension(new JitAttributesExtension());89addExtension(new TagExtension());90}9192/**93* Sets the preprocess options.94*95* @param options the preprocess options96*/97public void setOptions(Properties options) {98if (options != null) {99this.options.putAll(options);100}101this.options = options;102}103104/**105* Returns the preprocess options for this builder.106*107* @return the preprocess options108*/109public Properties getOptions() {110return this.options;111}112113/**114* Adds an extension to the builder.115*116* @param extension the extension to add117*/118public void addExtension(BuilderExtension extension) {119if (extension == null) {120throw new NullPointerException();121}122123BuilderExtension[] newExtensions = new BuilderExtension[extensions.length + 1];124if (extensions.length > 0) {125System.arraycopy(extensions, 0, newExtensions, 0, extensions.length);126}127newExtensions[newExtensions.length - 1] = extension;128this.extensions = newExtensions;129130extension.setBuilder(this);131}132133/**134* Returns the builder extensions/135*136* @return the builder extensions137*/138public BuilderExtension[] getExtensions() {139return extensions;140}141142/**143* Returns the logger associated with this builder.144*145* @return the logger146*/147public Logger getLogger() {148return logger;149}150151/**152* Sets this builder's logger.153*154* @param logger the new logger155*/156public void setLogger(Logger logger) {157this.logger = logger;158}159160/**161* Sets whether the build is incremental or not.162*163* @param isIncremental <code>true</code> if the build is incremental, <code>false</code> otherwise164*/165public void setIncremental(boolean isIncremental) {166this.isIncremental = isIncremental;167}168169/**170* Returns whether or not this builder will only do an incremental build.171*172* @return <code>true</code> if the build is incremental, <code>false</code> otherwise173*/174public boolean isIncremental() {175return this.isIncremental;176}177178/**179* Sets whether or not preprocessor metadata will be generated.180*181* @param enabledMetadata <code>true</code> if metadata is to be generated,182* <code>false</code> otherwise183*/184public void setMetadata(boolean enabledMetadata) {185this.enabledMetadata = enabledMetadata;186}187188/**189* Returns whether or not preprocessor metadata is enabled.190*191* @return <code>true</code> if metadata will be written, <code>false</code> otherwise192*/193public boolean isMetadataEnabled() {194return this.enabledMetadata;195}196197/**198* Sets whether or not the preprocessor should include files that do not199* have a INCLUDE-IF tag.200*201* @param include <code>true</code> if files with no INCLUDE-IF should202* be included, <code>false</code> otherwise203*/204public void setIncludeIfUnsure(boolean include) {205this.includeIfUnsure = include;206}207208/*[PR 117967] idea 491: Automatically create the jars required for test bootpath*/209/**210* Sets whether or not the preprocessor is running to generate Tests Boot Path project211*212* @param isTestsBoot <code>true</code> if preprocessor is running to generate Tests Boot Path project,213* <code>false</code> otherwise214*/215public void setIsTestsBoot(boolean isTestsBoot) {216this.isTestsBootPath = isTestsBoot;217}218219/*[PR 117967] idea 491: Automatically create the jars required for test bootpath*/220/**221* Sets whether or not the preprocessor should give warningsor errors about the files that do not222* have a INCLUDE-IF tag.223*224* @param warning <code>true</code> if files with no INCLUDE-IF should225* be marked with warning or error, <code>false</code> otherwise226*/227public void setNoWarnIncludeIf(boolean warning) {228this.noWarnIncludeIf = warning;229}230231/**232* Sets the configuration to preprocess.233*234* @param config the configuration to preprocess235*/236public void setConfiguration(ConfigObject config) {237if (config.isSet()) {238System.err.println("Warning: Builder is using " + config + ", a set, not a configuration.");239}240this.configObject = config;241this.registry = config.getRegistry();242this.outputDir = config.getOutputDir();243}244245/**246* Returns this builder's output directory.247*248* @return the output directory249*/250public File getOutputDir() {251return this.outputDir;252}253254/**255* Sets this builder's output directory.256*257* @param outputDir the new output directory258*/259public void setOutputDir(File outputDir) {260if (outputDir == null) {261throw new NullPointerException();262}263this.outputDir = outputDir;264}265266/**267* Returns this builder's configuration source directories.268*269* @return the config's source dirs270*/271public File getSourceDir() {272return this.sourceDir;273}274275/**276* Sets the proprocess job's source directory.277*278* @param sourceDir the source directory to preprocess279*/280public void setSourceDir(File sourceDir) {281if (sourceDir == null) {282throw new NullPointerException();283} else {284this.sourceDir = sourceDir;285}286}287288/**289* Set builder aware of other sources (to be used by the ExternalMessagesExtension).290*291* @param multipleSources <code>true</code> if there are other sources, <code>false</code> otherwise292*/293public void setMultipleSources(boolean multipleSources) {294this.multipleSources = multipleSources;295}296297/**298* Returns whether or not the configuration that setup this builder has multiple sources.299*300* @return <code>true</code> if there are other sources, <code>false</code> otherwise301*/302public boolean hasMultipleSources() {303return multipleSources;304}305306/**307* Performs the build.308*/309public boolean build() {310//create output dir even if no file is gonna be included in preprocess311getOutputDir().mkdirs();312if (validateOptions()) {313computeBuildFiles();314notifyBuildBegin();315316PreprocessorFactory factory = newPreprocessorFactory();317boolean force = isForced(this.options);318319//Ignore folders that do not exist (warning thrown in computeBuildFiles()320if (sourceDir != null) {321File metadataDir = new File(outputDir.getParentFile(), "jppmd");322String[] buildFiles = buildFilesBySourceDir.get(sourceDir);323getLogger().log("\nPreprocessing " + sourceDir.getAbsolutePath(), 1);324builtFileCount = 0;325326for (String buildFile : buildFiles) {327File sourceFile = new File(sourceDir, buildFile);328File outputFile = new File(outputDir, buildFile);329File metadataFile = new File(metadataDir, buildFile + ".jppmd");330331notifyBuildFileBegin(sourceFile, outputFile, buildFile);332333try (OutputStream metadataOutput = new PhantomOutputStream(metadataFile);334OutputStream output = new PhantomOutputStream(outputFile, force)) {335336// configure the preprocessor and let extensions do the same337JavaPreprocessor jpp;338339if (enabledMetadata) {340jpp = factory.newPreprocessor(metadataOutput, sourceFile, output, outputFile);341} else {342jpp = factory.newPreprocessor(sourceFile, output);343}344345Calendar cal = new GregorianCalendar();346if (!updateAllCopyrights) {347cal.setTime(new Date(sourceFile.lastModified()));348}349jpp.addValidFlags(registry.getValidFlags());350/*[PR 120411] Use a javadoc tag instead of TestBootpath preprocessor tag*/351jpp.setTestBootPath(isTestsBootPath);352notifyConfigurePreprocessor(jpp);353354// preprocess355boolean included = false;356try {357included = jpp.preprocess();358if (included) {359builtFileCount++;360}361handlePreprocessorWarnings(jpp, sourceFile);362} catch (Throwable t) {363handlePreprocessorException(t, sourceFile);364}365366if (!included && outputFile.exists()) {367outputFile.delete();368}369370if (!included && metadataFile.exists()) {371metadataFile.delete();372}373} catch (Throwable t) {374getLogger().log("Exception occured in file " + sourceFile.getAbsolutePath() + ", preprocess failed.", 3, t);375handleBuildException(t);376} finally {377notifyBuildFileEnd(sourceFile, outputFile, buildFile);378}379}380381logger.log(builtFileCount + " of " + buildFileCount + " file(s) included in preprocess", 1);382383/*[PR 118220] Incremental builder is not called when file is deleted in base library*/384List<String> deleteFiles = deleteFilesBySourceDir.get(sourceDir);385if (deleteFiles != null && deleteFiles.size() != 0) {386int deletedFilesCount = 0;387for (String file : deleteFiles) {388File deleteFile = new File(outputDir, file);389if (deleteFile.exists()) {390deletedFilesCount++;391deleteFile.delete();392}393}394getLogger().log(deletedFilesCount + " of " + deleteFileCount395+ " file(s) deleted in preprocess from " + outputDir.getAbsolutePath(), 1);396}397}398/*[PR 119753] classes.txt and AutoRuns are not updated when new test class is added */399List<String> buildResources = buildResourcesBySourceDir.get(sourceDir);400if (buildResources != null && buildResources.size() != 0) {401int copiedResourcesCount = 0;402int deletedResorucesCount = 0;403String outputpath;404if (isTestsBootPath) {405outputpath = configObject.getBootTestsOutputPath();406} else {407outputpath = configObject.getTestsOutputPath();408}409for (String file : buildResources) {410File resource_out = new File(outputpath, file);411File resource_src = new File(sourceDir, file);412if (resource_src.exists()) {413copyResource(resource_src, resource_out);414copiedResourcesCount++;415} else {416resource_out.delete();417deletedResorucesCount++;418}419}420421getLogger().log("Total Build Resource Count : " + buildResourcesCount, 1);422getLogger().log(" - " + copiedResourcesCount + " resource" + (copiedResourcesCount > 1 ? "s are " : " is ") + "copied to " + outputpath, 1);423getLogger().log(" - " + deletedResorucesCount + " resource" + (deletedResorucesCount > 1 ? "s are " : " is ") + "deleted from " + outputpath, 1);424}425426notifyBuildEnd();427}428429if (logger.getErrorCount() == 0) {430if (verdict) {431getLogger().log("PREPROCESS WAS SUCCESSFUL", 1);432}433return true;434} else {435if (verdict) {436getLogger().log("PREPROCESS WAS NOT SUCCESSFUL", 1);437}438return false;439}440}441442/*[PR 119753] classes.txt and AutoRuns are not updated when new test class is added */443public static void copyResource(File source, File destination) {444destination.delete();445446try {447SimpleCopy.copyFile(source, destination);448} catch (IOException e) {449System.err.println("ERROR - Could not copy the file to destination");450System.err.println(" Source: " + source.toString());451System.err.println(" Destination: " + destination.toString());452e.printStackTrace();453}454}455456/**457* Validates the build options.458*/459private boolean validateOptions() {460boolean isValid = true;461462if (configObject == null) {463configObject = registry.getConfiguration(options.getProperty("config"));464}465this.options.putAll(configObject.getOptions());466467// check for the verdict option468if (options.containsKey("verdict")) {469this.verdict = true;470}471472if (options.containsKey("includeifunsure")) {473setIncludeIfUnsure(true);474}475if (options.containsKey("nowarnincludeif")) {476setNoWarnIncludeIf(true);477}478479if (options.containsKey("nowarninvalidflags")) {480this.noWarnInvalidFlags = true;481}482483if (options.containsKey("updateallcopyrights")) {484this.updateAllCopyrights = true;485}486487// call the method for all the extensions488String extensionName = "";489try {490for (BuilderExtension extension : extensions) {491extensionName = extension.getName();492extension.validateOptions(this.options);493}494} catch (BuilderConfigurationException e) {495logger.log("A configuration exception occured", Logger.SEVERITY_FATAL, e);496isValid = false;497} catch (Exception e) {498StringBuffer buffer = new StringBuffer("An exception occured while invoking validateOptions() for the extension \"");499buffer.append(extensionName);500buffer.append("\"");501logger.log(buffer.toString(), Logger.SEVERITY_ERROR, e);502}503return isValid;504}505506/**507* Notifies the extensions that the build is beginning.508*/509private void notifyBuildBegin() {510// call the method for all the extensions511String extensionName = "";512try {513for (BuilderExtension extension : extensions) {514extensionName = extension.getName();515logger.setMessageSource(extensionName);516extension.notifyBuildBegin();517logger.setMessageSource(null);518}519} catch (Exception e) {520StringBuffer buffer = new StringBuffer("An exception occured while invoking notifyBuildBegin() for the extension \"");521buffer.append(extensionName);522buffer.append("\"");523logger.log(buffer.toString(), Logger.SEVERITY_ERROR, e);524}525}526527/**528* Notifies the extensions that the build is ending.529*/530private void notifyBuildEnd() {531// call the method for all the extensions532String extensionName = "";533try {534for (BuilderExtension extension : extensions) {535extensionName = extension.getName();536logger.setMessageSource(extensionName);537extension.notifyBuildEnd();538logger.setMessageSource(null);539}540} catch (Exception e) {541StringBuffer buffer = new StringBuffer("An exception occured while invoking notifyBuildEnd() for the extension \"");542buffer.append(extensionName);543buffer.append("\"");544logger.log(buffer.toString(), Logger.SEVERITY_ERROR, e);545}546}547548/**549* Notifies the extensions that the build is beginning on the specified550* file.551*/552private void notifyBuildFileBegin(File sourceFile, File outputFile, String relativePath) {553// call the method for all the extensions554String extensionName = "";555try {556for (BuilderExtension extension : extensions) {557extensionName = extension.getName();558logger.setMessageSource(extensionName);559extension.notifyBuildFileBegin(sourceFile, outputFile, relativePath);560logger.setMessageSource(null);561}562} catch (Exception e) {563StringBuffer buffer = new StringBuffer("An exception occured while invoking notifyBuildFileBegin() for the extension \"");564buffer.append(extensionName);565buffer.append("\"");566logger.log(buffer.toString(), Logger.SEVERITY_ERROR, e);567}568}569570/**571* Notifies the extensions that the build is ending on the specified file.572*/573private void notifyBuildFileEnd(File sourceFile, File outputFile, String relativePath) {574// call the method for all the extensions575String extensionName = "";576try {577for (BuilderExtension extension : extensions) {578extensionName = extension.getName();579logger.setMessageSource(extensionName);580extension.notifyBuildFileEnd(sourceFile, outputFile, relativePath);581logger.setMessageSource(null);582}583} catch (Exception e) {584StringBuffer buffer = new StringBuffer("An exception occured while invoking notifyBuildFileEnd() for the extension \"");585buffer.append(extensionName);586buffer.append("\"");587logger.log(buffer.toString(), Logger.SEVERITY_ERROR, e);588}589}590591/**592* Notifies the extensions that they should configure the preprocessor.593*/594private void notifyConfigurePreprocessor(JavaPreprocessor preprocessor) {595preprocessor.setIncludeIfUnsure(this.includeIfUnsure);596preprocessor.setNoWarnIncludeIf(this.noWarnIncludeIf);597598// call the method for all the extensions599String extensionName = "";600try {601for (BuilderExtension extension : extensions) {602extensionName = extension.getName();603logger.setMessageSource(extensionName);604extension.notifyConfigurePreprocessor(preprocessor);605logger.setMessageSource(null);606}607} catch (Exception e) {608StringBuffer buffer = new StringBuffer("An exception occured while invoking notifyConfigurePreprocessor() for the extension \"");609buffer.append(extensionName);610buffer.append("\"");611logger.log(buffer.toString(), Logger.SEVERITY_ERROR, e);612}613}614615/**616* Handles exceptions thrown while building.617*/618private void handleBuildException(Throwable t) {619if (t instanceof Error) {620logger.log("An error occured while building", Logger.SEVERITY_FATAL, t);621throw (Error) t;622} else {623logger.log("An exception occured while building", Logger.SEVERITY_ERROR, t);624}625}626627/**628* Handles exceptions thrown by the preprocessor.629*/630private void handlePreprocessorException(Throwable t, File sourceFile) {631if (t instanceof Error) {632logger.log("An error occured while invoking the preprocessor", "preprocessor", Logger.SEVERITY_FATAL, sourceFile, t);633throw (Error) t;634} else {635logger.log("An exception occured while invoking the preprocessor", "preprocessor", Logger.SEVERITY_ERROR, sourceFile, t);636}637}638639/**640* Handles warnings generated by the preprocessor.641*/642private void handlePreprocessorWarnings(JavaPreprocessor jpp, File sourceFile) {643if (jpp.hasWarnings()) {644for (PreprocessorWarning warning : jpp.getWarnings()) {645int severity = warning.shouldFail() ? Logger.SEVERITY_ERROR : Logger.SEVERITY_WARNING;646/*[PR 117967] idea 491: Automatically create the jars required for test bootpath*/647if (warning.getMessage().startsWith("No INCLUDE-IF") && sourceFile.getAbsolutePath().endsWith(".java") && !includeIfUnsure && !isTestsBootPath) {648severity = Logger.SEVERITY_ERROR;649}650651if (warning.getMessage().startsWith("Ignoring copyright")) {652severity = Logger.SEVERITY_INFO;653}654655logger.log(warning.getMessage(), "preprocessor", severity, sourceFile, warning.getLine(), warning.getCharstart(), warning.getCharend());656}657}658659if (!noWarnInvalidFlags) {660for (PreprocessorWarning warning : jpp.getInvalidFlags()) {661logger.log(warning.getMessage(), "preprocessor", Logger.SEVERITY_ERROR, sourceFile, warning.getLine(), warning.getCharstart(), warning.getCharend());662}663}664}665666/**667* Determines whether the specified source file should be built.668*/669private boolean shouldBuild(File sourceFile, File outputFile, String relativePath) {670// call the method for all the extensions671for (BuilderExtension extension : extensions) {672logger.setMessageSource(extension.getName());673boolean shouldBuild = extension.shouldBuild(sourceFile, outputFile, relativePath);674logger.setMessageSource(null);675if (!shouldBuild) {676return false;677}678}679680return true;681}682683/*[PR 118220] Incremental builder is not called when file is deleted in base library*/684/**685* Returns the deleted Files686*/687/*[PR 119753] classes.txt and AutoRuns are not updated when new test class is added */688private List<String> getDeletedFiles(File sourceDir) {689// call the method for all the extensions690for (BuilderExtension extension : extensions) {691logger.setMessageSource(extension.getName());692List<String> elements = extension.getDeleteFiles(sourceDir);693logger.setMessageSource(null);694if (elements != null) {695return elements;696}697}698699return null;700}701702/*[PR 119753] classes.txt and AutoRuns are not updated when new test class is added */703private List<String> getBuildResources(File sourceDir) {704// call the method for all the extensions705for (BuilderExtension extension : extensions) {706logger.setMessageSource(extension.getName());707List<String> elements = extension.getBuildResources(sourceDir);708logger.setMessageSource(null);709if (elements != null) {710return elements;711}712}713return null;714}715716/**717* Creates a new PreprocessorFactory object.718*/719private PreprocessorFactory newPreprocessorFactory() {720PreprocessorFactory factory = new PreprocessorFactory();721/*[PR 117967] idea 491: Automatically create the jars required for test bootpath*/722factory.setFlags(this.configObject.getFlagsAsArray());723factory.setRequiredIncludeFlags(this.configObject.getRequiredIncludeFlagSet());724return factory;725}726727/**728* Recursively searches the given root directory to find all files. The file729* paths are returned, relative to the root directory.730*/731private List<String> getFiles(File rootDirectory) {732List<String> fileList = new ArrayList<>();733File[] files = rootDirectory.listFiles();734735if (files == null) {736StringBuffer msg = new StringBuffer("Error reading the source directory \"");737msg.append(rootDirectory.getAbsolutePath());738msg.append("\" - No Files copied");739getLogger().log(msg.toString(), 2);740verdict = false;741} else {742getFiles(files, "", fileList);743}744745return fileList;746}747748/**749* This is a helper function to getFiles(File);750*/751private static void getFiles(File[] files, String relativePath, List<String> fileList) {752for (File file : files) {753if (file.isFile()) {754fileList.add(relativePath + file.getName());755} else {756String childRelativePath = relativePath + file.getName() + File.separator;757getFiles(file.listFiles(), childRelativePath, fileList);758}759}760}761762private void computeBuildFiles() {763if (sourceDir.exists()) {764List<String> allFiles = getFiles(sourceDir);765List<String> buildFiles = new ArrayList<>(allFiles.size());766for (int j = 0; j < allFiles.size(); j++) {767String currentFile = allFiles.get(j).toString();768if (shouldBuild(sourceDir, outputDir, currentFile)) {769buildFiles.add(currentFile);770}771}772773String[] buildFilesArray = buildFiles.toArray(new String[buildFiles.size()]);774buildFilesBySourceDir.put(sourceDir, buildFilesArray);775buildFileCount += buildFilesArray.length;776/*[PR 118220] Incremental builder is not called when file is deleted in base library*/777/*[PR 119753] classes.txt and AutoRuns are not updated when new test class is added */778List<String> deleteFiles = getDeletedFiles(sourceDir);779if (deleteFiles != null && deleteFiles.size() != 0) {780deleteFileCount = deleteFiles.size();781deleteFilesBySourceDir.put(sourceDir, deleteFiles);782}783784List<String> buildResources = getBuildResources(sourceDir);785if (buildResources != null && buildResources.size() != 0) {786buildResourcesCount = buildResources.size();787buildResourcesBySourceDir.put(sourceDir, buildResources);788}789} else {790logger.log("Error: Source directory does not exist: " + sourceDir.getAbsolutePath(), Logger.SEVERITY_ERROR, new NullPointerException());791sourceDir = null;792}793}794795/**796* Returns the number of files preprocessed.797*798* @return the number of files preprocessed799*/800public int getBuildFileCount() {801return buildFileCount;802}803804}805806807