Path: blob/master/sourcetools/com.ibm.jpp.preprocessor/com/ibm/jpp/om/JavaPreprocessor.java
6004 views
/*******************************************************************************1* Copyright (c) 1999, 2021 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.BufferedReader;24import java.io.File;25import java.io.FileInputStream;26import java.io.FileNotFoundException;27import java.io.FileOutputStream;28import java.io.IOException;29import java.io.InputStreamReader;30import java.io.OutputStream;31import java.io.OutputStreamWriter;32import java.io.PrintWriter;33import java.io.Writer;34import java.nio.charset.Charset;35import java.nio.charset.StandardCharsets;36import java.text.NumberFormat;37import java.text.ParseException;38import java.util.ArrayList;39import java.util.Collection;40import java.util.HashMap;41import java.util.HashSet;42import java.util.LinkedList;43import java.util.List;44import java.util.Map;45import java.util.NoSuchElementException;46import java.util.Set;47import java.util.Stack;48import java.util.StringTokenizer;49import java.util.regex.Pattern;5051/**52* This class pre-processes the contents of an input stream, providing53* a mechanism for text exclusion which is similar to the #ifdef mechanism54* in the C pre-processor.55* <p>56* It looks for expressions of the form:57* <pre>58* /*[IF flag1 | flag2 & (flag3 ˆ flag4)]*/59* "lines to include if the expression is true"60* /*[ENDIF]*/61* </pre>62* (Note: No space between last "*" and last "/")63* <p>64* The commands must be the first item on the line, and they may be nested.65* <p>66* Note that the source is assumed to be 8859_1 text.67*68* @author IBM Corp.69* @version initial70*/71public class JavaPreprocessor {7273private static final class IfState {7475/**76* The current IF or ELSEIF branch is active.77*/78static final int IF_ACTIVE = 0;7980/**81* A previous IF or ELSEIF branch was selected.82*/83static final int IF_PREVIOUS = 1;8485/**86* The current IF or ELSEIF branch is inactive87* (and no previous branch was selected).88*/89static final int IF_INACTIVE = 2;9091/**92* We're in an active ELSE branch.93*/94static final int ELSE_ACTIVE = 3;9596/**97* We're in an inactive ELSE branch.98*/99static final int ELSE_INACTIVE = 4;100101final boolean outerActive;102103int state;104105IfState(boolean active, Stack<IfState> stack) {106super();107this.outerActive = stack.isEmpty() || stack.peek().isActive();108this.state = active ? IF_ACTIVE : IF_INACTIVE;109}110111boolean isActive() {112switch (state) {113case IF_ACTIVE:114case ELSE_ACTIVE:115return outerActive;116default:117return false;118}119}120121}122123private static final Charset charset;124125static {126if ("z/OS".equalsIgnoreCase(System.getProperty("os.name"))) { //$NON-NLS-1$ //$NON-NLS-2$127charset = Charset.forName("IBM-1047"); //$NON-NLS-1$128} else {129charset = StandardCharsets.US_ASCII;130}131}132133private final File inFile;134private BufferedReader in;135private final Stack<BufferedReader> inStack;136private final Writer out;137138/**139* Configurable options. The following can be configured by setters before140* preprocess() is called.141*/142private final Set<String> flags = new HashSet<>();143// Flags that are required in an INCLUDE-IF for the file to be included144private Set<String> requiredIncludeFlags = new HashSet<>();145// Flags which were ref'ed by source code.146private List<String> foundFlags = new ArrayList<>();147// The PRs found fixed in the code (using the PR command)148private Set<String> fixedPRs = new HashSet<>();149private final Set<String> newIDEAs = new HashSet<>();150private Set<String> jxeRules = new HashSet<>();151private List<PreprocessorWarning> invalidFlags = new ArrayList<>();152// Property collection containing key-value pairs declared in MSG command153private Map<String, String> externalMessages;154// Inline messages flag155private boolean replaceMsg_getString = false;156private boolean jxePreserveApi = false;157private final Map<String, String> macros = new HashMap<>();158private final Map<String, Integer> numericMacros = new HashMap<>();159private JitAttributes jitAttributes;160// Whether files with no INCLUDE-IF commands should be included161private boolean includeIfUnsure = false;162// whether to give warning about the files that do not have include if directive163/* [PR 117967] idea 491: Automatically create the jars required for test bootpath */164private boolean noWarnIncludeIf = false;165private final Stack<IfState> ifResults = new Stack<>();166private int lineCount = 0;167private int outputLineCount = 0;168private boolean echo = true;169private final Set<String> validFlags = new HashSet<>();170171// Stores MSG-command key-value pairs until shouldInclude is determined,172// at which point the entries may be added to externalMessages173final Map<String, String> externalMessagesToBeAdded = new HashMap<>();174175// variables to handle generating jxe rules for public api176private String packageName = null;177private String rootClassName = null;178179private boolean useMacros = false;180private MsgCallInliner msgCallInliner;181182char[] line = new char[0]; // current line183int lineLength = 0; // Length of the current line.184private static final int MAX_COMMAND = 16; // Maximum size of a command.185private String command = ""; // Current command (only 1).186private String argument = ""; // Current argument (only 1).187188private final List<PreprocessorWarning> warnings = new LinkedList<>();189private PreprocessorWarning error = null;190private int numberOfIncludeIfs = 0;191private boolean shouldInclude;192/*193* true iff this file should be included based on the first INCLUDE-IF directive. If no INCLUDE-IF directive is found in the194* file, this will be set to the value of the flag "includeIfUnsure"195*/196private long timeTaken = 0;197private int numberOfBlankLines = 0;198// continuation of a long line199private final boolean continuation = false;200private boolean foundCopyright = false;201// name of file being processed202private final String file;203private Writer metadataOut;204205private static final String newLine = System.lineSeparator();206207private final MsgClassSubstituter msgClassSubstituter = new MsgClassSubstituter();208boolean substituteMsgClass = false;209210private boolean addMSGcomment = false;211private String msg_comment;212/* [PR 120411] Use a javadoc tag instead of TestBootpath preprocessor tag */213private boolean isTestBootpathJavaDocTagWritten = false;214private boolean preprocessingTestBootpath = false;215private boolean testBootpathJavaDocIsNeeded = false;216217final void replaceLine(String replacement) {218line = replacement.toCharArray();219lineLength = line.length;220}221222/**223* Create the new instance of the preprocessor.224*225* @param metadataOut226* @param inputFile the input file handle227* @param out stream to write the converted output on228* @param outputFile the output file handle229*/230public JavaPreprocessor(OutputStream metadataOut, File inputFile, OutputStream out, File outputFile) {231super();232this.file = inputFile.getAbsolutePath();233this.inStack = new Stack<>();234this.inFile = inputFile;235this.out = new OutputStreamWriter(out, charset);236237if (metadataOut != null) {238this.metadataOut = new OutputStreamWriter(metadataOut, charset);239try {240this.metadataOut.write(inputFile.getAbsolutePath());241this.metadataOut.write(newLine);242this.metadataOut.write(outputFile.getAbsolutePath());243this.metadataOut.write(newLine);244} catch (IOException ex) {245error("IOException on write: " + ex.getMessage(), ex);246}247}248}249250/**251* Adds flags to the list of valid flags.252*253* @param flags the flags to be added254*/255public void addValidFlags(Set<String> flags) {256validFlags.addAll(flags);257}258259/**260* Sets whether preprocessing for bootpath project or not261* @param bootPath true if bootPath project, otherwise false262*263*/264/* [PR 120411] Use a javadoc tag instead of TestBootpath preprocessor tag */265public void setTestBootPath(boolean bootPath) {266preprocessingTestBootpath = bootPath;267}268269/**270* Removes all the valid flags from this Java preprocessor.271*/272public void removeValidFlags() {273validFlags.clear();274}275276/**277* Checks if the given string matches one of the current state flags.278*279* @param arg String the flag to test.280* @return boolean true if we the string is defined and false otherwise.281*/282private boolean defined(String arg) {283return flags.contains(arg);284}285286/**287* Invoke the current command/argument pair.288*/289private void doCommand() {290String cmd = command;291292if (cmd.length() <= 0) {293error("Missing command");294}295296String arg = argument.trim();297298if (cmd.equals("IF")) {299doCommand_IF(arg);300} else if (cmd.equals("ENDIF")) {301doCommand_ENDIF(arg);302} else if (cmd.equals("ELSE")) {303doCommand_ELSE(arg);304} else if (cmd.equals("ELSEIF")) {305doCommand_ELSEIF(arg);306} else if (cmd.equals("PR") || cmd.equals("BUG")) {307doCommand_PR(arg);308} else if (cmd.equals("IDEA")) {309doCommand_IDEA(arg);310} else if (cmd.equals("INCLUDE-IF")) {311doCommand_INCLUDEIF(arg);312} else if (cmd.equals("MSG")) {313doCommand_MSG(arg);314} else if (cmd.equals("JXERULE")) {315doCommand_JXERULE(arg);316} else if (cmd.equals("REM")) {317//318} else if (cmd.equals("EXCLUDE-FILE")) {319numberOfIncludeIfs++; // INCLUDE-IF replacement320} else if (cmd.equals("USEMACROS")) {321doCommand_USEMACROS();322} else if (cmd.equals("#INCLUDE")) {323doCommand_SHARPINCLUDE(arg);324} else {325error("Bogus command -- \"" + cmd + "\"");326}327}328329/**330* Handle the ELSE command.331*332* @param arg String the expression to test.333*/334private void doCommand_ELSE(String arg) {335if (ifResults.empty()) {336error("ELSE with no IF");337}338339IfState top = ifResults.peek();340341switch (top.state) {342case IfState.IF_ACTIVE:343case IfState.IF_PREVIOUS:344top.state = IfState.ELSE_INACTIVE;345break;346case IfState.IF_INACTIVE:347top.state = IfState.ELSE_ACTIVE;348break;349case IfState.ELSE_ACTIVE:350case IfState.ELSE_INACTIVE:351error("ELSE after ELSE");352break;353}354355echo = top.isActive();356}357358/**359* Handle the ELSEIF command.360*361* @param arg String the expression to test.362*/363private void doCommand_ELSEIF(String arg) {364if (ifResults.empty()) {365error("ELSEIF with no IF");366}367368boolean satisfied;369370if (arg.isEmpty()) {371satisfied = false;372} else {373try {374ExpressionResult parseResult = ExpressionParser.parse(arg, this);375376if (!parseResult.isBoolean) {377error(String.format("boolean expression required: '%s'", arg)); //$NON-NLS-1$378return;379}380381satisfied = parseResult.value != 0;382} catch (ParseException pe) {383error(pe);384return;385}386}387388IfState top = ifResults.peek();389390switch (top.state) {391case IfState.IF_ACTIVE:392top.state = IfState.IF_PREVIOUS;393break;394case IfState.IF_INACTIVE:395if (satisfied) {396top.state = IfState.IF_ACTIVE;397}398break;399case IfState.IF_PREVIOUS:400break;401case IfState.ELSE_ACTIVE:402case IfState.ELSE_INACTIVE:403error("ELSEIF after ELSE");404break;405}406407echo = top.isActive();408}409410/**411* Handle the PR command.412*413* @param arg String the PR number to be added.414*/415private void doCommand_PR(String arg) {416fixedPRs.add(arg);417}418419/**420* Handle the IDEA command.421*422* @param arg String the PR number to be added.423*/424private void doCommand_IDEA(String arg) {425newIDEAs.add(arg);426}427428/**429* Handle the INCLUDEIF command.430*431* @param arg String the boolean expression involving flags432*/433private void doCommand_INCLUDEIF(String arg) {434numberOfIncludeIfs++;435if (numberOfIncludeIfs != 1) {436warning("Extra INCLUDE-IF directive found on line " + lineCount + " will be ignored.");437return;438}439if (arg.isEmpty()) {440shouldInclude = false;441} else {442try {443/* [PR 120411] Use a javadoc tag instead of TestBootpath preprocessor tag */444if (preprocessingTestBootpath) {445if (arg.indexOf("TestBootpath") != -1) {446testBootpathJavaDocIsNeeded = true;447}448}449450ExpressionResult parseResult = ExpressionParser.parse(arg, this);451452if (!parseResult.isBoolean) {453error(String.format("boolean expression required: '%s'", arg)); //$NON-NLS-1$454return;455}456457boolean expressionResult = parseResult.value != 0;458459boolean forceExclude = false;460if (expressionResult) {461for (String flag : flags) {462if (requiredIncludeFlags.contains(flag)) {463forceExclude = true;464int pos = -1;465while (pos <= arg.length()) {466pos = arg.indexOf(flag, pos + 1);467if (pos == -1) {468break;469}470if (isWordStart(arg, pos) && isWordEnd(arg, pos + flag.length())) {471forceExclude = false;472break;473}474}475if (!forceExclude) {476break;477}478}479}480}481shouldInclude = forceExclude ? false : expressionResult;482} catch (ParseException pe) {483error(pe);484return;485}486}487}488489/**490* Is the character at the given index the start of a word?491* That is, there's only whitespace between the preceding492* punctuation (if any) and that character.493*/494private static boolean isWordStart(String text, int index) {495while (index > 0 && Character.isWhitespace(text.charAt(index - 1))) {496index -= 1;497}498return (index == 0) || isOperatorOrBracket(text.charAt(index - 1));499}500501/**502* Is the character at the given index the end of a word?503* That is, there's only whitespace between the following504* punctuation (if any) and that character.505*/506private static boolean isWordEnd(String text, int index) {507int length = text.length();508while (index < length && Character.isWhitespace(text.charAt(index))) {509index += 1;510}511return (index == length) || isOperatorOrBracket(text.charAt(index));512}513514/**515* Handle the ENDIF command.516*517* @param arg String the expression to test.518*/519private void doCommand_ENDIF(String arg) {520if (ifResults.empty()) {521error("ENDIF with no IF");522}523524ifResults.pop();525echo = ifResults.empty() || ifResults.peek().isActive();526}527528/**529* Handle the IF command.530*531* @param arg String the expression to test.532*/533private void doCommand_IF(String arg) {534boolean satisfied;535536if (arg.isEmpty()) {537satisfied = false;538} else {539try {540ExpressionResult parseResult = ExpressionParser.parse(arg, this);541542if (!parseResult.isBoolean) {543error(String.format("boolean expression required: '%s'", arg)); //$NON-NLS-1$544return;545}546547satisfied = parseResult.value != 0;548} catch (ParseException pe) {549error(pe);550return;551}552}553554IfState top = new IfState(satisfied, ifResults);555556ifResults.push(top);557echo = top.isActive();558}559560/**561* Handle the MSG command562*563* @param arg String the argument to this MSG command.564*/565private void doCommand_MSG(String arg) {566if (externalMessages == null || !echo) {567return;568}569570try {571MSGArg msgArg = new MSGArg(arg);572String key = msgArg.getKey();573String value = msgArg.getValue();574boolean store = true;575576// First check to see if the key and value exist already577if (externalMessages.containsKey(key)) {578if (!value.equals(externalMessages.get(key))) {579throw new SyntaxException("\"" + key + "\" already has the value \"" + externalMessages.get(key) + "\"");580}581} else if (externalMessages.containsValue(value)) {582warning("MSG command warning: \"" + value + "\" is given multiple keys");583}584585if (externalMessagesToBeAdded.containsKey(key)) {586if (value.equals(externalMessagesToBeAdded.get(key))) {587store = false;588} else {589throw new SyntaxException("\"" + key + "\" already has the value \"" + externalMessagesToBeAdded.get(key) + "\"");590}591} else if (externalMessagesToBeAdded.containsValue(value)) {592warning("MSG command warning: \"" + value + "\" is given multiple keys");593}594595if (store) {596externalMessagesToBeAdded.put(key, value);597}598/* [PR 119681] Desing:952 - Core.JCL : Add external messages in generated source */599if (msgCallInliner == null || msgCallInliner.inlineKeys) {600value = value.replaceAll("\n", "\\\\n");601value = value.replaceAll("\b", "\\\\b");602value = value.replaceAll("\t", "\\\\t");603value = value.replaceAll("\f", "\\\\f");604value = value.replaceAll("\r", "\\\\r");605msg_comment = "// " + key + " = " + value;606addMSGcomment = true;607}608} catch (SyntaxException e) {609warning("MSG command syntax error, command ignored: MSG " + arg + " - Detail: " + e.getMessage());610}611}612613/**614* Handles the JXERULE command. The quoted text following this command is615* added to the java/lang/jxeLink.rules file. Command syntax example: <code>616* [JXERULE "-includeField java.io.FileInputStream:java.io.FileInputStream.fd"]617* </code>618*/619private void doCommand_JXERULE(String arg) {620if (!echo) {621return;622}623624StringBuilder buff = new StringBuilder();625boolean quoted = false;626boolean escapeChar = false;627int argLength = arg.length();628for (int i = 0; i < argLength; i++) {629char current = arg.charAt(i);630if (quoted) {631if (escapeChar) {632buff.append(current);633escapeChar = false;634} else {635if (current == '\\') {636escapeChar = true;637} else if (current == '"') {638quoted = false;639} else {640buff.append(current);641}642}643} else {644if (current == '"') {645quoted = true;646} else if (!Character.isWhitespace(current)) {647warning("JXERULE command warning: Unquoted argument for command");648return;649}650}651}652String rule = buff.toString();653654if (rule.length() > 0 && jxeRules != null) {655jxeRules.add(rule);656}657}658659/**660* Tells the preprocessor to search for macros on the following line.661*/662private void doCommand_USEMACROS() {663this.useMacros = true;664}665666/**667* @param arg668*/669private void doCommand_SHARPINCLUDE(String arg) {670if (!(arg.startsWith("/") || arg.startsWith(":", 1))) {671arg = inFile.getParent() + File.separator + arg;672}673674try {675BufferedReader includeReader = new BufferedReader(new InputStreamReader(new FileInputStream(arg), charset));676677includeReader.mark(1024);678679String line = includeReader.readLine();680681if (line.indexOf("/*[INCLUDE-IF") == -1) {682includeReader.reset();683}684685inStack.push(in);686in = includeReader;687} catch (FileNotFoundException e) {688error("Could not find #include file: " + arg, e);689} catch (IOException e) {690error("IO error when loading #include file: " + arg, e);691} catch (Exception e) {692error("Error when loading #include file: " + arg, e);693}694}695696/** ********************** end class MSGArg ************************** */697698/**699* Handle the ATTR command700*701* @param arg String the argument to this ATTR command.702*/703@SuppressWarnings("unused")704private void doCommand_ATTR(String arg) {705/* If we are inside an IF directive, do not process the attribute. */706if (!echo) {707return;708}709710StringTokenizer st = new StringTokenizer(arg);711String type = null;712try {713type = st.nextToken();714715if (type.equals("Trusted")) {716try {717int argNum = Integer.valueOf(st.nextToken()).intValue();718jitAttributes.addTrustedMethodOrClass(st.nextToken(), argNum);719} catch (NumberFormatException e) {720warning("ATTR command warning: Expecting integer argument for 'Trusted'");721}722} else if (type.equals("Untrusted")) {723try {724int argNum = Integer.valueOf(st.nextToken()).intValue();725jitAttributes.addUntrustedMethod(st.nextToken(), argNum);726} catch (NumberFormatException e) {727warning("ATTR command warning: Expecting integer argument for 'Untrusted'");728}729} else if (type.equals("Recognized")) {730jitAttributes.addRecognizedMethod(st.nextToken());731} else if (type.equals("SkipNullChecks")) {732jitAttributes.skipNullChecksFor(st.nextToken());733} else if (type.equals("SkipBoundChecks")) {734jitAttributes.skipBoundChecksFor(st.nextToken());735} else if (type.equals("SkipCheckCasts")) {736jitAttributes.skipCheckCastsFor(st.nextToken());737} else if (type.equals("SkipDivChecks")) {738jitAttributes.skipDivChecksFor(st.nextToken());739} else if (type.equals("SkipArrayStoreChecks")) {740jitAttributes.skipArrayStoreChecksFor(st.nextToken());741} else if (type.equals("SkipChecksOnArrayCopies")) {742jitAttributes.skipChecksOnArrayCopiesFor(st.nextToken());743} else if (type.equals("SkipZeroInitializationOnNewarrays")) {744jitAttributes.skipZeroInitializationOnNewarraysFor(st.nextToken());745} else {746warning("ATTR command warning: bogus type '" + type + "'");747}748} catch (PreprocessorException ppe) {749warning(ppe.toString());750} catch (NoSuchElementException e) {751if (type == null) {752warning("ATTR command warning: no type specified");753} else {754warning("ATTR command warning: expecting another argument for type '" + type + "'");755}756return;757}758if (st.hasMoreTokens()) {759warning("ATTR command warning: ignoring extra arguments");760}761}762763/**764* Represents and parses MSG command arguments.765*/766private static final class MSGArg {767private final String arg;768private final String key;769private final String value;770private int begin;771private int end;772773/**774* Construct an instance of this class which parses the string arg.775*776* @param arg String the command argument to parse777* @throws SyntaxException If arg does not have the correct syntax for778* an MSG argument string779*/780MSGArg(String arg) throws SyntaxException {781this.arg = arg;782key = determineKey();783value = determineValue();784}785786/**787* Answers the receiver's key string788*789* @return String the receiver's key string790*/791String getKey() {792return key;793}794795/**796* Answers the receiver's value string797*798* @return String the receiver's value string799*/800String getValue() {801return value;802}803804/**805* Beginning at an offset this.begin in this.arg, this method steps past806* leading whitespace then returns the quoted string following this807* whitespace, in the process advancing this.end to point to the closing808* quote for this quoted string.809*810* @return String the next quoted string811*/812private String getNextQuotedString() throws SyntaxException {813while (Character.isWhitespace(arg.charAt(begin))) {814begin++;815}816817if (arg.charAt(begin) != '"') {818throw new SyntaxException("A non-whitespace character occurred where only whitespace should.");819}820821begin++;822end = indexOfUnescaped(arg, '"', begin);823if (end == -1) {824throw new SyntaxException("Non-terminated quoted string.");825}826827return substringUnescaped(arg, '"', begin, end);828}829830/**831* Determines and answers the key string for the command argument being832* parsed. Advances this.end to point to the closing quote following the833* key string.834*835* @return String the key836* @throws SyntaxException837*/838private String determineKey() throws SyntaxException {839// get the key840begin = 0;841return getNextQuotedString();842}843844/**845* If called following a call to getKey(), this method will determine846* and answer the value string. A precondition for this method is that847* 'end' must point to a character which is followed by whitespace,848* which must, in turn, be followed by the quoted value string849*850* @return String the receiver's value string851*/852private String determineValue() throws SyntaxException {853// get the value854begin = end + 1;855while (Character.isWhitespace(arg.charAt(begin))) {856begin++;857}858859if (arg.charAt(begin) != ',') {860throw new SyntaxException("A comma and optional whitespace expected between quoted key and value strings.");861}862863begin++;864String value = getNextQuotedString();865int index = value.indexOf('\\');866867if (index < 0 || index + 1 >= value.length()) {868return value;869}870871int last = 0;872int from = 0;873StringBuilder result = new StringBuilder();874while (index >= 0) {875int special = "\\btnfr".indexOf(value.charAt(index + 1));876if (special >= 0) {877result.append(value.substring(from, index));878result.append("\\\b\t\n\f\r".charAt(special));879last = index + 2;880from = index + 2;881} else {882last = index + 1;883}884index = value.indexOf('\\', last);885}886887if (from == 0) {888return value;889}890result.append(value.substring(from));891return result.toString();892}893}894895/** ********************** end class MSGArg ************************** */896897/**898* Finds the index, greater than or equal to fromIndex, of a 'non-escaped'899* instance of the character ch in the string str. 'non-escaped' means not900* directly preceded by an odd number of backslashes. Returns -1 if an901* unescaped instance of ch is not found.902*903* @param str String the string to search904* @param ch int the int value of the character to search for905* @param fromIndex int the offset in str at which to begin the search906*/907static int indexOfUnescaped(String str, int ch, int fromIndex) {908int result = -1;909boolean escaped = true;910while (escaped) {911result = str.indexOf(ch, fromIndex);912escaped = false;913int i = result - 1;914while (i >= 0 && str.charAt(i) == '\\') {915escaped = !escaped;916i--;917}918fromIndex = result + 1;919}920return result;921}922923static String substringUnescaped(String str, char escapedChar, int begin, int end) {924StringBuilder strBuffer = new StringBuilder(str);925for (int i = begin; i < end; i++) {926if (strBuffer.charAt(i) == '\\' && strBuffer.charAt(i + 1) == escapedChar) {927strBuffer.deleteCharAt(i);928end--;929}930}931return strBuffer.substring(begin, end);932}933934/**935* Answers a new string based on str in which all instances of charToEscape936* are preceded by a backslash.937*938* @param str String the string to escape939* @param charToEscape char the character to be escaped940* @return String the escaped version of str941*/942static String escape(String str, char charToEscape) {943StringBuilder buf = new StringBuilder(str);944int i = 0;945int lengthDiff = 0;946while ((i = str.indexOf(charToEscape, i)) != -1) {947buf.insert(i + lengthDiff, '\\');948lengthDiff++;949i++;950}951return buf.toString();952}953954/**955* Each preprocessor may use a single instance of this class to replace956* specially-formatted calls to Msg.getString() with appropriate957* String-valued expressions at preprocess time.958*/959private final class MsgCallInliner {960// arrays and method calls961private int pos;962private int closeMethod;963private String lineString;964private StringBuilder lineBuf;965final boolean inlineKeys;966private int callIdx;967968MsgCallInliner(boolean inlineKeys) {969super();970this.inlineKeys = inlineKeys;971}972973void replaceMsg_getString() throws SyntaxException {974boolean argumentsSupplied = true;975lineString = new String(line, 0, lineLength);976lineBuf = new StringBuilder(lineString);977978// Determine if a reference to Msg.getString is on this line979// otherwise return980callIdx = lineString.indexOf("com.ibm.oti.util.Msg.getString");981if (callIdx == -1) {982callIdx = lineString.indexOf("com.ibm.oti.rmi.util.Msg.getString");983if (callIdx == -1) {984callIdx = lineString.indexOf("Msg.getString");985if (callIdx == -1) {986return;987}988pos = callIdx + 13;989} else {990pos = callIdx + 34;991}992} else {993pos = callIdx + 30;994}995skipWhite();996997if (line[pos] != '(') {998// Make sure it is a method call999return;1000}1001pos++;1002skipWhite();10031004if (line[pos] != '"') {1005// Make sure the first argument is a string literal1006return;1007}1008pos++;10091010// Find position of closing ")" for method call1011closeMethod = closeParenthesis(true, pos - 1);1012if (closeMethod == -1) {1013// Make sure the method call terminates on this line1014throw new SyntaxException("Call to Msg.getString() spanned multiple lines in '" + file + "'");1015}10161017// Take key string and begin replacement message string1018int begin = pos;1019pos = indexOfUnescaped(lineString, '"', begin);1020String key = substringUnescaped(lineString, '"', begin, pos);1021String messageStr = inlineKeys ? key : externalMessagesToBeAdded.get(key);1022if (messageStr == null) {1023throw new SyntaxException("Call to Msg.getString() specified a key string with no corresponding MSG statement.");1024}10251026if (!advanceIfObjectArray()) {1027pos++;1028skipWhite();1029if (line[pos] == ')') {1030argumentsSupplied = false; // no arguments supplied1031} else if (line[pos] != ',') {1032throw new SyntaxException("Comma expected after literal key string in Msg.getString() call.");1033} else {1034pos++;1035}1036}10371038// int callEnd = lineString.indexOf(')', pos);1039messageStr = escape(messageStr, '"');1040/* [PR 120571] "\n" in external messages causes error */1041// messageStr = escape(messageStr, '\n');1042messageStr = messageStr.replaceAll("\n", "\\\\n");1043messageStr = messageStr.replaceAll("\b", "\\\\b");1044messageStr = messageStr.replaceAll("\t", "\\\\t");1045messageStr = messageStr.replaceAll("\f", "\\\\f");1046messageStr = messageStr.replaceAll("\r", "\\\\r");10471048messageStr = "\"" + messageStr + "\"";1049lineBuf.replace(callIdx, closeMethod + 1, messageStr);1050if (argumentsSupplied) {1051getAndSubstituteObjectStrings(callIdx + messageStr.length() - 1);1052}1053substituteLine();1054}10551056private boolean advanceIfObjectArray() {1057int i = lineString.indexOf("new", pos);1058// added pos so that it will not check before current position1059if (i != -1 && i < closeMethod) {1060for (i += 3; Character.isWhitespace(line[i]); i++) {1061// skip whitespace1062}10631064if (lineString.regionMatches(i, "Object", 0, 6)) {1065for (i += 6; Character.isWhitespace(line[i]); i++) {1066// skip whitespace1067}10681069if (line[i] == '[') {1070for (i += 1; Character.isWhitespace(line[i]); i++) {1071// skip whitespace1072}10731074if (line[i] == ']') {1075for (i += 1; Character.isWhitespace(line[i]); i++) {1076// skip whitespace1077}10781079if (line[i] == '{') {1080pos = i + 1;1081return true;1082}1083}1084}1085}1086}1087return false;1088}10891090private void skipWhite() {1091while (Character.isWhitespace(line[pos])) {1092pos++;1093}1094}10951096private void getAndSubstituteObjectStrings(int messageEnd) throws SyntaxException {1097int objNumber = 0;1098do {1099String objString = getNextObjectString();1100String objStub = "{" + NumberFormat.getNumberInstance().format(objNumber) + "}";1101String lineBufStr = lineBuf.toString();1102int stubIdx = lineBufStr.indexOf(objStub, callIdx);1103if (!inlineKeys && (stubIdx == -1 || stubIdx >= messageEnd)) {1104throw new SyntaxException("Call to Msg.getString() specified to many objects.");1105}11061107if (inlineKeys) {1108int start = messageEnd + 1;1109int end = messageEnd + 1;1110String appendString = " + \" " + objStub + "=\" + " + objString;1111messageEnd += appendString.length();1112lineBuf.replace(start, end, appendString);1113lineBufStr = lineBuf.toString();1114} else {1115// KRLW changed to handle using an argument multiple times1116String originalObjString = objString;1117do {1118int start = stubIdx;1119int end = stubIdx + objStub.length();11201121if (stubIdx == callIdx + 1) {1122objString = objString + "+\"";1123start--;1124messageEnd--;1125} else if (stubIdx + objStub.length() == messageEnd) {1126objString = "\"+" + objString;1127end++;1128messageEnd--;1129} else {1130objString = "\"+" + objString + "+\"";1131}11321133messageEnd += objString.length() - objStub.length();1134lineBuf.replace(start, end, objString);11351136lineBufStr = lineBuf.toString();1137stubIdx = lineBufStr.indexOf(objStub, callIdx);1138objString = originalObjString;1139} while (stubIdx > -1 && stubIdx < messageEnd);1140}1141objNumber++;1142} while (line[pos++] == ',');11431144String lineBufStr = lineBuf.toString();1145while (lineBufStr.indexOf("+\"\"+") > -1) {1146int idx = lineBufStr.indexOf("+\"\"+");1147lineBuf.replace(idx, idx + 4, "+");1148lineBufStr = lineBuf.toString();1149}1150}11511152private String getNextObjectString() throws SyntaxException {1153// boolean createdObject = false;1154skipWhite();1155int begin = pos;11561157// If a string literal, pull it out and return it1158if (lineString.charAt(begin) == '"') {1159pos = indexOfUnescaped(lineString, '"', begin + 1);1160pos++;1161return substringUnescaped(lineString, '"', begin, pos);1162}11631164// Make sure this is a valid java identifier or an opening1165// parenthesis1166if (!(Character.isJavaIdentifierStart(line[pos]) || line[pos] == '(')) {1167throw new SyntaxException("Object arguments to Msg.getString() and elements of the Object[] argument to Msg.getString() must be specified as literal strings or variable identifiers");1168}1169// while(Character.isJavaIdentifierPart(line[pos]))1170// pos++;11711172boolean inString = false;1173boolean inSingleQuote = false;1174int parenCount = 0;1175int squigleCount = 0;1176--pos;1177do {1178++pos;1179while (line[pos] != ',' || inString || inSingleQuote) {1180if (!inSingleQuote && pos == indexOfUnescaped(lineString, '"', pos)) {1181inString = !inString;1182}1183if (!inString && pos == indexOfUnescaped(lineString, '\'', pos)) {1184inSingleQuote = !inSingleQuote;1185}11861187if (!(inString || inSingleQuote)) {1188if (line[pos] == '(') {1189parenCount++;1190} else if (line[pos] == ')') {1191parenCount--;1192} else if (line[pos] == '{') {1193squigleCount++;1194} else if (line[pos] == '}') {1195squigleCount--;1196}11971198if (parenCount == -1) {1199break;1200}1201if (squigleCount == -1) {1202break;1203}1204}1205pos++;1206}1207} while (parenCount > 0 || squigleCount > 0);12081209// Check the validity of the arguments.1210char ch = line[pos];1211if (!(Character.isWhitespace(ch) || ch == ')' || ch == ',' || ch == '}')) {1212throw new SyntaxException("Object arguments to Msg.getString() and elements of the Object[] argument to Msg.getString() must be specified as literal strings or variable identifiers");1213}12141215String objString = lineString.substring(begin, pos);12161217skipWhite();1218return objString;1219}12201221private void substituteLine() {1222replaceLine(lineBuf.toString());1223}12241225private int closeParenthesis(boolean inString, int index) {1226int parenthesisCount = 1;1227boolean inSingleQuote = false;12281229while (parenthesisCount > 0 && index < (line.length - 1)) {1230index++;1231if (!inSingleQuote && index == indexOfUnescaped(lineString, '"', index)) {1232inString = !inString;1233}1234if (!inString && index == indexOfUnescaped(lineString, '\'', index)) {1235inSingleQuote = !inSingleQuote;1236}12371238if (!(inString || inSingleQuote)) {1239if (line[index] == '(') {1240parenthesisCount++;1241} else if (line[index] == ')') {1242parenthesisCount--;1243}1244}1245}12461247if (parenthesisCount > 0) {1248return -1;1249}1250return index;1251}12521253}12541255/** ********************** end class MsgCallInliner ***************** */12561257/**1258* Each preprocessor may use a single instance of this class to replace1259* the name of the class used to lookup messages1260*/1261private final class MsgClassSubstituter {1262private final static String GET_STRING = ".getString";1263private final static String DEFAULT_MSG_CLASS = "com.ibm.oti.util.Msg";1264private final static String DEFAULT_UNQUALIFIED_MSG_CLASS = "Msg";1265private final static String DEFAULT_MSG_GET_STRING = DEFAULT_MSG_CLASS + GET_STRING;1266private final static String DEFAULT_UNQUALIFIED_MSG_GET_STRING = DEFAULT_UNQUALIFIED_MSG_CLASS + GET_STRING;1267// arrays and method calls1268private int pos;1269private int closeMethod;1270private String lineString;1271private StringBuilder lineBuf;1272private String msgClassName;1273private int callIdx;1274int classEnd = 0;12751276MsgClassSubstituter() {1277super();1278}12791280public void setMessageClassName(String msgClassName) {1281this.msgClassName = msgClassName;1282}12831284void replaceMsgClass() throws SyntaxException {1285lineString = new String(line, 0, lineLength);1286lineBuf = new StringBuilder(lineString);12871288// Determine if a reference to Msg.getString is on this line1289// otherwise1290// return1291callIdx = lineString.indexOf(DEFAULT_MSG_GET_STRING);1292if (callIdx == -1) {1293callIdx = lineString.indexOf(DEFAULT_UNQUALIFIED_MSG_GET_STRING);1294if (callIdx == -1) {1295return;1296}1297pos = callIdx + DEFAULT_UNQUALIFIED_MSG_GET_STRING.length();1298classEnd = callIdx + DEFAULT_UNQUALIFIED_MSG_CLASS.length();1299} else {1300pos = callIdx + DEFAULT_MSG_GET_STRING.length();1301classEnd = callIdx + DEFAULT_MSG_CLASS.length();1302}1303skipWhite();13041305if (line[pos] != '(') {1306// Make sure it is a method call1307return;1308}1309pos++;1310skipWhite();13111312if (line[pos] != '"') {1313// Make sure the first argument is a string literal1314return;1315}1316pos++;13171318// Find position of closing ")" for method call1319closeMethod = closeParenthesis(true, pos - 1);1320if (closeMethod == -1) {1321// Make sure the method call terminates on this line1322throw new SyntaxException("Call to Msg.getString() spanned multiple lines in '" + file + "'");1323}13241325// now replace the class used1326lineBuf.replace(callIdx, classEnd, msgClassName);13271328substituteLine();1329}13301331private void skipWhite() {1332while (Character.isWhitespace(line[pos])) {1333pos++;1334}1335}13361337private int closeParenthesis(boolean inString, int index) {1338int parenthesisCount = 1;1339boolean inSingleQuote = false;13401341while (parenthesisCount > 0 && index < (line.length - 1)) {1342index++;1343if (!inSingleQuote && index == indexOfUnescaped(lineString, '"', index)) {1344inString = !inString;1345}1346if (!inString && index == indexOfUnescaped(lineString, '\'', index)) {1347inSingleQuote = !inSingleQuote;1348}13491350if (!(inString || inSingleQuote)) {1351if (line[index] == '(') {1352parenthesisCount++;1353} else if (line[index] == ')') {1354parenthesisCount--;1355}1356}1357}13581359if (parenthesisCount > 0) {1360return -1;1361}1362return index;1363}13641365private void substituteLine() {1366replaceLine(lineBuf.toString());1367}13681369}13701371/** ********************** end class MsgCallInliner ***************** */13721373/**1374* Exception class used in parsing MSG commands1375*/1376private static final class SyntaxException extends Exception {1377/**1378* serialVersionUID1379*/1380private static final long serialVersionUID = 3256728398444573747L;13811382/**1383* Syntax exception constructor.1384*1385* @param msg the exception message1386*/1387public SyntaxException(String msg) {1388super(msg);1389}1390}13911392/** ********************** end class SyntaxException ***************** */13931394private static final class ExpressionScanner {13951396static final class Token {13971398final int kind;13991400final int offset;14011402final String text;14031404Token(int kind, String text, int offset) {1405super();1406this.kind = kind;1407this.offset = offset;1408this.text = text;1409}14101411}14121413// tokens1414static final int TK_EOI = 0;1415static final int TK_IDENT = 1;1416static final int TK_NUMBER = 2;1417static final int TK_LEFT_PAREN = 3;1418static final int TK_RIGHT_PAREN = 4;1419static final int TK_AND = 5;1420static final int TK_NOT = 6;1421static final int TK_OR = 7;1422static final int TK_XOR = 8;1423static final int TK_LESS_THAN = 9;1424static final int TK_LESS_EQUAL = 10;1425static final int TK_EQUAL = 11;1426static final int TK_NOT_EQUAL = 12;1427static final int TK_GREATER_EQUAL = 13;1428static final int TK_GREATER_THAN = 14;14291430private int index;14311432private final String input;14331434private final int length;14351436private Token nextToken;14371438ExpressionScanner(String input) {1439super();1440this.input = input;1441this.length = input.length();1442this.nextToken = null;1443}14441445private void skipWhitespace() {1446while (index < length && Character.isWhitespace(input.charAt(index))) {1447index += 1;1448}1449}14501451Token getNextToken() throws ParseException {1452Token token = nextToken;14531454if (token != null) {1455nextToken = null;1456return token;1457}14581459skipWhitespace();14601461if (index >= length) {1462return new Token(TK_EOI, "", index);1463}14641465int start = index;1466char nextChar = input.charAt(start);14671468index += 1;14691470switch (nextChar) {1471case '(':1472token = new Token(TK_LEFT_PAREN, "(", start);1473break;14741475case ')':1476token = new Token(TK_RIGHT_PAREN, ")", start);1477break;14781479case '&':1480token = new Token(TK_AND, "&", start);1481break;14821483case '!':1484if (index < length && input.charAt(index) == '=') {1485index += 1;1486token = new Token(TK_NOT_EQUAL, "!=", start);1487} else {1488token = new Token(TK_NOT, "!", start);1489}1490break;14911492case '|':1493token = new Token(TK_OR, "|", start);1494break;14951496case '^':1497token = new Token(TK_XOR, "^", start);1498break;14991500case '<':1501if (index < length && input.charAt(index) == '=') {1502index += 1;1503token = new Token(TK_LESS_EQUAL, "<=", start);1504} else {1505token = new Token(TK_LESS_THAN, "<", start);1506}1507break;15081509case '=':1510if (index < length && input.charAt(index) == '=') {1511index += 1;1512token = new Token(TK_EQUAL, "==", start);1513} else {1514throw new ParseException("unrecognized token '='", start);1515}1516break;15171518case '>':1519if (index < length && input.charAt(index) == '=') {1520index += 1;1521token = new Token(TK_GREATER_EQUAL, ">=", start);1522} else {1523token = new Token(TK_GREATER_THAN, ">", start);1524}1525break;15261527default:1528if (Character.isDigit(nextChar)) {1529while (index < length && Character.isDigit(input.charAt(index))) {1530index += 1;1531}15321533String number = input.substring(start, index);15341535token = new Token(TK_NUMBER, number, start);1536} else {1537while (index < length && !isOperatorOrBracket(input.charAt(index))) {1538index += 1;1539}15401541String id = input.substring(start, index).trim();15421543token = new Token(TK_IDENT, id, start);1544}1545break;1546}15471548return token;1549}15501551void pushBackToken(Token token) {1552if (nextToken != null) {1553throw new IllegalStateException();1554}15551556nextToken = token;1557}15581559}15601561private static final class ExpressionResult {15621563final boolean isBoolean;15641565final int value;15661567ExpressionResult(boolean value) {1568super();1569this.isBoolean = true;1570this.value = value ? 1 : 0;1571}15721573ExpressionResult(int value) {1574super();1575this.isBoolean = false;1576this.value = value;1577}15781579}15801581private static final class ExpressionParser {15821583/*1584* Expression1585* : Term1586* | Expression '&' Term1587* | Expression '|' Term1588* | Expression '^' Term1589*1590* Term1591* : Primary1592* | Primary '<' Primary1593* | Primary '<=' Primary1594* | Primary '==' Primary1595* | Primary '!=' Primary1596* | Primary '>=' Primary1597* | Primary '>' Primary1598*1599* Primary1600* : Identifier1601* | Number1602* | '!' Primary1603* | '(' Expression ')'1604*/1605static ExpressionResult parse(String input, JavaPreprocessor processor) throws ParseException {1606ExpressionScanner scanner = new ExpressionScanner(input);1607ExpressionParser parser = new ExpressionParser(processor, scanner);1608ExpressionResult result = parser.parseExpression();1609ExpressionScanner.Token token = scanner.getNextToken();16101611if (token.kind == ExpressionScanner.TK_EOI) {1612return result;1613} else {1614throw new ParseException("malformed expression", token.offset);1615}1616}16171618/**1619* Evaluate a combination of two boolean values.1620*/1621private static boolean combineBoolean(int lhs, int operator, int rhs) {1622boolean lhsBool = lhs != 0;1623boolean rhsBool = rhs != 0;16241625switch (operator) {1626case ExpressionScanner.TK_AND:1627return lhsBool & rhsBool;1628case ExpressionScanner.TK_OR:1629return lhsBool | rhsBool;1630case ExpressionScanner.TK_XOR:1631return lhsBool ^ rhsBool;1632default:1633throw new IllegalArgumentException();1634}1635}16361637/**1638* Evaluate a comparison of two numeric values.1639*/1640private static boolean compareNumeric(int lhs, int operator, int rhs) {1641switch (operator) {1642case ExpressionScanner.TK_LESS_THAN:1643return lhs < rhs;1644case ExpressionScanner.TK_LESS_EQUAL:1645return lhs <= rhs;1646case ExpressionScanner.TK_EQUAL:1647return lhs == rhs;1648case ExpressionScanner.TK_NOT_EQUAL:1649return lhs != rhs;1650case ExpressionScanner.TK_GREATER_EQUAL:1651return lhs >= rhs;1652case ExpressionScanner.TK_GREATER_THAN:1653return lhs > rhs;1654default:1655throw new IllegalArgumentException();1656}1657}16581659private final JavaPreprocessor processor;16601661private final ExpressionScanner scanner;16621663private ExpressionParser(JavaPreprocessor processor, ExpressionScanner scanner) {1664super();1665this.processor = processor;1666this.scanner = scanner;1667}16681669/*1670* Expression1671* : Term1672* | Expression '&' Term1673* | Expression '|' Term1674* | Expression '^' Term1675*/1676private ExpressionResult parseExpression() throws ParseException {1677ExpressionResult result = parseTerm();16781679for (;;) {1680ExpressionScanner.Token token = scanner.getNextToken();16811682switch (token.kind) {1683case ExpressionScanner.TK_AND:1684case ExpressionScanner.TK_OR:1685case ExpressionScanner.TK_XOR:1686ExpressionResult rhs = parseTerm();16871688if (result.isBoolean & rhs.isBoolean) {1689result = new ExpressionResult(combineBoolean(result.value, token.kind, rhs.value));1690} else {1691throw new ParseException(token.text + " requires boolean operands", token.offset);1692}1693break;16941695default:1696scanner.pushBackToken(token);1697return result;1698}1699}1700}17011702/*1703* Term1704* : Primary1705* | Primary '<' Primary1706* | Primary '<=' Primary1707* | Primary '==' Primary1708* | Primary '!=' Primary1709* | Primary '>=' Primary1710* | Primary '>' Primary1711*/1712private ExpressionResult parseTerm() throws ParseException {1713ExpressionResult result = parsePrimary();1714ExpressionScanner.Token token = scanner.getNextToken();17151716switch (token.kind) {1717case ExpressionScanner.TK_LESS_THAN:1718case ExpressionScanner.TK_LESS_EQUAL:1719case ExpressionScanner.TK_EQUAL:1720case ExpressionScanner.TK_NOT_EQUAL:1721case ExpressionScanner.TK_GREATER_EQUAL:1722case ExpressionScanner.TK_GREATER_THAN:1723ExpressionResult rhs = parsePrimary();17241725if (result.isBoolean | rhs.isBoolean) {1726throw new ParseException(token.text + " requires numeric operands", token.offset);1727} else {1728result = new ExpressionResult(compareNumeric(result.value, token.kind, rhs.value));1729}1730break;17311732default:1733scanner.pushBackToken(token);1734break;1735}17361737return result;1738}17391740/*1741* Primary1742* : Identifier1743* | Number1744* | '!' Primary1745* | '(' Expression ')'1746*/1747private ExpressionResult parsePrimary() throws ParseException {1748ExpressionResult result;1749ExpressionScanner.Token token = scanner.getNextToken();17501751switch (token.kind) {1752case ExpressionScanner.TK_IDENT:1753result = processor.resolve(token.text);1754break;17551756case ExpressionScanner.TK_NUMBER:1757result = new ExpressionResult(Integer.parseInt(token.text));1758break;17591760case ExpressionScanner.TK_NOT:1761result = parsePrimary();1762if (result.isBoolean) {1763result = new ExpressionResult(result.value == 0);1764} else {1765throw new ParseException(token.text + " requires a boolean operand", token.offset);1766}1767break;17681769case ExpressionScanner.TK_LEFT_PAREN:1770result = parseExpression();1771token = scanner.getNextToken();1772if (token.kind != ExpressionScanner.TK_RIGHT_PAREN) {1773throw new ParseException("expected ')'", token.offset);1774}1775break;17761777default:1778throw new ParseException("malformed expression", token.offset);1779}17801781return result;1782}17831784}17851786/**1787* Print out an error message and quit.1788*/1789private void error(String msg) {1790error(msg, null);1791}17921793private void error(String msg, Exception cause) {1794if (msg == null) {1795msg = "";1796}1797this.error = new PreprocessorWarning(msg, lineCount, 0, lineLength);1798String fullMessage = msg + " [" + file + ", line " + lineCount + ']';1799writeLog(fullMessage);1800PreprocessorException exception = new PreprocessorException(fullMessage);1801if (cause != null) {1802exception.initCause(cause);1803}1804throw exception;1805}18061807private void error(ParseException pe) {1808String msg = pe.getMessage();1809if (msg == null) {1810msg = "";1811}1812this.error = new PreprocessorWarning(msg, lineCount, 0, lineLength);1813String fullMessage = msg + " [" + file + ", line " + lineCount + ", column " + pe.getErrorOffset() + ']';1814writeLog(fullMessage);1815PreprocessorException exception = new PreprocessorException(fullMessage);1816exception.initCause(pe);1817throw exception;1818}18191820/**1821* Write a message to the log file.1822*/1823private static void writeLog(String msg) {1824final String logFileName = "jpp-error.txt";1825if (msg == null || msg.equals("")) {1826return;1827}1828try (FileOutputStream fos = new FileOutputStream(logFileName, true);1829PrintWriter out = new PrintWriter(fos)) {1830out.println(msg);1831} catch (IOException e) {1832// ignore exceptions1833}1834}18351836/**1837* Answer the number of lines which have been preprocessed.1838*1839* @return int the number of lines that have been preprocessed.1840*/1841public int getLineCount() {1842return lineCount;1843}18441845/**1846* Output a line break to the output stream.1847*/1848private void newLine() {1849try {1850if (echo) {1851out.write(newLine);1852}1853} catch (IOException ex) {1854error("IOException on write", ex);1855}1856}18571858/**1859* Process the streams. Answer if the stream should be included based on the1860* first INCLUDE-IF directive found.1861* <p>1862* @return boolean the result of the first INCLUDE-IF directive in the1863* input, or if none is found, true iff the flag "includeIfUnsure"1864* is defined (this is a hack until all source files have an1865* INCLUDE-IF directive)1866*/1867public boolean preprocess() {1868long start = System.currentTimeMillis();1869FileInputStream fis = null;18701871try {1872fis = new FileInputStream(inFile);1873this.in = new BufferedReader(new InputStreamReader(fis, charset));1874} catch (FileNotFoundException e) {1875error("File not found: " + inFile.getAbsolutePath(), e);1876}18771878{ // populate the map of numeric macros1879final Pattern Numeric = Pattern.compile("\\d+");18801881numericMacros.clear();18821883for (Map.Entry<String, String> entry : macros.entrySet()) {1884String value = entry.getValue();18851886if (Numeric.matcher(value).matches()) {1887numericMacros.put(entry.getKey(), Integer.valueOf(value));1888}1889}1890}18911892/* [PR] The state machine based version was too brittle. Here is a simpler, line base version. */1893// parameter initialization1894testBootpathJavaDocIsNeeded = false;1895isTestBootpathJavaDocTagWritten = false;18961897// assume a copyright notice is present1898foundCopyright = true;18991900while (readLine()) {1901if (addMSGcomment) {1902try {1903for (int i = 0; i < line.length && (line[i] == '\t' || line[i] == ' '); ++i) {1904out.write(line[i]);1905}1906out.write(msg_comment);1907newLine();1908} catch (IOException e) {1909error("IOException on write: " + e.getMessage(), e);1910}1911addMSGcomment = false;1912msg_comment = "";1913}1914if (replaceMsg_getString && echo) {1915try {1916msgCallInliner.replaceMsg_getString();1917} catch (SyntaxException e) {1918warning("Improper usage of Msg.getString(). Method call could not be removed from code. Detail: " + e.getMessage());1919}1920}19211922if (substituteMsgClass && echo) {1923try {1924msgClassSubstituter.replaceMsgClass();1925} catch (SyntaxException e) {1926warning("Improper usage of Msg.getString(). Message class could not be substituted. Detail: " + e.getMessage());1927}1928}19291930if (!continuation && findCommand()) {1931doCommand();1932if (!shouldInclude) {1933break;1934}1935} else {1936if (!shouldInclude) {1937break;1938}19391940// check for class definition1941if (jxePreserveApi && echo) {1942if (rootClassName == null) {1943checkPackageDeclaration();1944}1945checkClassDeclaration();1946}1947// replace macros1948if (useMacros && macros != null) {1949replaceMacros();1950useMacros = false;1951}1952if ((lineLength != 0) || (numberOfBlankLines < 1)) {1953// don't print the line if it's a blank and we've already1954// printed enough blanks1955writeLine();1956}1957}1958}19591960timeTaken = System.currentTimeMillis() - start;1961if (numberOfIncludeIfs == 0) {1962if (!noWarnIncludeIf) {1963if (includeIfUnsure) {1964warnings.add(new PreprocessorWarning("No INCLUDE-IF directives found. File will be INCLUDED", lineCount, 0, lineLength, false));1965} else {1966warnings.add(new PreprocessorWarning("No INCLUDE-IF directives found. File will be EXCLUDED", lineCount, 0, lineLength, false));1967}1968}1969} else if (shouldInclude && !foundCopyright) {1970warning("No copyright");1971}19721973if (shouldInclude) {1974externalMessages.putAll(externalMessagesToBeAdded);1975}19761977try {1978if (fis != null) {1979fis.close();1980}19811982if (in != null) {1983in.close();1984}19851986if (this.metadataOut != null) {1987this.metadataOut.flush();1988}19891990if (this.out != null) {1991this.out.flush();1992}1993} catch (IOException e) {1994error(e.toString(), e);1995}19961997return shouldInclude;1998}19992000/**2001* Scans the current line for a public class definition. If one is found, it2002* is preserved by including a <code>-includeLibraryClass</code> rule in the2003* jxeRules collection.2004*/2005private void checkClassDeclaration() {2006try {2007String line = new String(this.line, 0, lineLength);2008if (line.indexOf("class") == -1 && line.indexOf("interface") == -1) {2009return;2010}20112012boolean isDefinition = false;2013boolean isPublic = false;2014StringTokenizer tokenizer = new StringTokenizer(line, " \n\r\t", false);2015String className = null;2016while (tokenizer.hasMoreTokens()) {2017String token = tokenizer.nextToken();2018if (token.equals("public")) {2019isPublic = true;2020continue;2021} else if (token.equals("private") || token.equals("protected")) {2022return;2023} else if (token.equals("static") || token.equals("final") || token.equals("abstract")) {2024continue;2025} else if (token.equals("class") || token.equals("interface")) {2026isDefinition = true;2027continue;2028} else if (isDefinition && isPublic) {2029// this is the identifier2030className = token;2031break;2032} else {2033return;2034}2035}2036if (className == null || className.equals(rootClassName)) {2037return;2038}20392040if (this.rootClassName != null) {2041// this is a inner class2042className = this.rootClassName + "$" + className;2043} else {2044this.rootClassName = className;2045}20462047if (this.packageName != null) {2048className = this.packageName + "." + className;20492050if (packageName.startsWith("java.") || packageName.startsWith("javax.")) {2051jxeRules.add("-includeLibraryClass " + className);2052}2053}2054} catch (StringIndexOutOfBoundsException e) {2055// ignore2056}2057}20582059/**2060* Scans the current line for a package declaration.2061*/2062private void checkPackageDeclaration() {2063try {2064String line = new String(this.line, 0, this.lineLength);2065int posPackage = line.indexOf("package");2066if (posPackage == -1) {2067return;2068}20692070int posIdentifierStart = posPackage + "package".length();20712072while (!Character.isJavaIdentifierStart(line.charAt(posIdentifierStart))) {2073posIdentifierStart++;2074}20752076int posIdentifierEnd = posIdentifierStart + 1;2077while (!(line.charAt(posIdentifierEnd) == ';')) {2078posIdentifierEnd++;2079}20802081this.packageName = line.substring(posIdentifierStart, posIdentifierEnd).trim();2082} catch (StringIndexOutOfBoundsException e) {2083// ignore2084}2085}20862087/**2088* Replaces macro identifiers with their associated values on this line.2089* Macro identifiers are denoted by %%MACRO@macroname%%, where macroname is2090* the name of the macro to retrieve the value from. The macro values are2091* stored in the macros Properties object, set in the constructor.2092*/2093private void replaceMacros() {2094final String macroStartId = "%%MACRO@";2095final String macroEndId = "%%";2096String line = new String(this.line, 0, this.lineLength);2097int posMacroStart = 0;2098int posMacroEnd = 0;2099while ((posMacroStart = line.indexOf(macroStartId, posMacroEnd)) != -1) {2100int posIdentifierStart = posMacroStart + macroStartId.length();2101int posIdentifierEnd = line.indexOf(macroEndId, posIdentifierStart);2102if (posIdentifierEnd == -1) {2103warning("Unterminated macro");2104return;2105}2106if (posIdentifierEnd <= posIdentifierStart) {2107error("A macro identifer must be at least one character long");2108return;2109}21102111posMacroEnd = posIdentifierEnd + macroEndId.length();21122113String identifier = line.substring(posIdentifierStart, posIdentifierEnd);2114String replacement = macros.get(identifier);2115if (replacement == null) {2116warning("A replacement for the macro \"" + identifier + "\" could not be found");2117replacement = "";2118}21192120StringBuilder newLine = new StringBuilder(line.length() - identifier.length() + replacement.length());2121if (posMacroStart > 0) {2122newLine.append(line.substring(0, posMacroStart));2123}2124newLine.append(replacement);2125if (posMacroEnd < line.length() - 1) {2126newLine.append(line.substring(posMacroEnd, line.length()));2127}21282129line = newLine.toString();21302131// line has now changed in size thus we need to update posMacroEnd otherwise2132// it will point to some other place and indexOf(str,int) will give us a wrong value2133posMacroEnd = (posMacroEnd + replacement.length()) - (identifier.length() + macroStartId.length() + macroEndId.length());2134}2135replaceLine(line);2136}21372138/**2139* Read the next line of input. If there are no characters available answer2140* false, otherwise fill the input line buffer with the characters,2141* excluding the trailing LF, remember the line length, and answer true.2142* Strip out all CR characters. If the line is too long to buffer, then just2143* assume it isn't a command and write it back out.2144*2145* @return boolean2146*/2147private boolean readLine() {2148try {2149for (;;) {2150String nextLine = in.readLine();21512152if (nextLine != null) {2153int length = nextLine.length();21542155while (length > 0) {2156char lastCh = nextLine.charAt(length - 1);21572158if ((lastCh == '\n') || (lastCh == '\r')) {2159length -= 1;2160} else {2161break;2162}2163}21642165line = nextLine.substring(0, length).toCharArray();2166lineCount += 1;2167lineLength = line.length;2168return true;2169} else {2170in.close();2171in = null;21722173if (inStack.isEmpty()) {2174return false;2175}21762177in = inStack.pop();2178}2179}2180} catch (IOException ex) {2181error("IOException on read", ex);2182}21832184return true;2185}21862187/**2188* If we are currently echoing, then write out the current line.2189*/2190private void writeLine() {2191if (echo) {2192try {2193out.write(line, 0, lineLength);2194/* [PR 120411] Use a javadoc tag instead of TestBootpath preprocessor tag */2195if (preprocessingTestBootpath & !isTestBootpathJavaDocTagWritten & testBootpathJavaDocIsNeeded) {2196newLine();2197out.write("\n/**\n * @TestBootpath\n */");2198newLine();2199isTestBootpathJavaDocTagWritten = true;2200}22012202if (metadataOut != null) {2203StringBuilder sb = new StringBuilder();2204sb.append(lineCount);2205sb.append(":");2206sb.append(++outputLineCount);2207metadataOut.write(sb.toString());2208metadataOut.write(newLine);2209}2210if (!continuation) {2211newLine();2212}22132214if (lineLength == 0) {2215numberOfBlankLines++;2216} else {2217numberOfBlankLines = 0;2218}2219} catch (IOException ex) {2220error("IOException on write: " + ex.getMessage(), ex);2221}2222}2223}22242225/**2226* Detect if the current line has a command in it. If not, just return2227* false. If it does have a command, pull out the command and argument2228* pieces, and then return true.2229*/2230private boolean findCommand() {2231command = "";22322233int i = 0;22342235// Skip whitespace before the command.2236while (i < lineLength && Character.isWhitespace(line[i])) {2237i += 1;2238}22392240// Give up if it doesn't start with the command indicator.2241if (i > (lineLength - 4) || line[i] != '/' || line[i + 1] != '*' || line[i + 2] != '[') {2242return false;2243}22442245i += 3;22462247// Get the command.2248int cmdStart = i;2249int cmdLen = 0;22502251for (; cmdLen <= MAX_COMMAND && i < lineLength; ++cmdLen, ++i) {2252char ch = line[i];22532254if (ch == ']' || Character.isWhitespace(ch)) {2255break;2256}2257}22582259command = new String(line, cmdStart, cmdLen).toUpperCase();22602261// Skip whitespace before the argument.2262while (i < lineLength && Character.isWhitespace(line[i])) {2263i += 1;2264}22652266// Find the last ']' on the line to allow arguments to include that2267// character, but this means that ']' cannot be used in comments2268// after the command on the same line.2269int positionOfClose = lineLength;22702271for (int pos = i; pos < lineLength; ++pos) {2272if (line[pos] == ']') {2273positionOfClose = pos;2274}2275}22762277// Get the argument if there is one.2278argument = new String(line, i, positionOfClose - i);22792280return true;2281}22822283private static final String SeparatorChars = "()|!&^<=>";22842285/**2286* Answer true iff the char is an operator or bracket2287* (i.e. in the set { '&', '^', '|', '!', '(', ')', '<', '=', '>' })2288*2289* @param c char the char to inspect2290*/2291static boolean isOperatorOrBracket(char c) {2292return SeparatorChars.indexOf(c) != -1;2293}22942295/**2296* Checks if the given string matches one of the current state flags, check2297* if this flag is a valid flag and add it to the list of incorrect flags if2298* it isn't.2299*2300* @param flag the flag to test.2301* @return boolean true if we the string is defined and false otherwise.2302*/2303private boolean checkFlag(String flag) {2304synchronized (foundFlags) {2305if (!foundFlags.contains(flag)) {2306foundFlags.add(flag);2307}2308}2309if (!validFlags.contains(flag)) {2310if (flag == null) {2311flag = "";2312}2313invalidFlags.add(new PreprocessorWarning("The flag " + flag + " is not valid.", lineCount, 0, lineLength));2314}2315return defined(flag);2316}23172318/**2319* Resolve the value of the given identifier.2320*/2321ExpressionResult resolve(String identifier) {2322Integer macro = numericMacros.get(identifier);23232324if (macro != null) {2325return new ExpressionResult(macro.intValue());2326} else {2327return new ExpressionResult(checkFlag(identifier));2328}2329}23302331/**2332* Adds a warning to the internal list of warnings.2333*2334* @param s the warning to add2335*/2336private void warning(String s) {2337if (s == null) {2338s = "";2339}2340warnings.add(new PreprocessorWarning(s, lineCount, 0, lineLength));2341}23422343/**2344* Answers true iff the preprocessor has found things in the input it would2345* like to warn about2346*2347* @return boolean if warnings exist2348*/2349public boolean hasWarnings() {2350return warnings.size() > 0;2351}23522353/**2354* Returns an iterator over any warnings found in the inputStream2355*2356* @return List a List of warning Strings2357*/2358public List<PreprocessorWarning> getWarnings() {2359return warnings;2360}23612362/**2363* Returns a collection containing the invalid flags.2364*2365* @return the invalid flags collection2366*/2367public Collection<PreprocessorWarning> getInvalidFlags() {2368return invalidFlags;2369}23702371/**2372* Returns the time taken to preprocess2373*2374* @return long the time taken to preprocess2375*/2376public long getTime() {2377return timeTaken;2378}23792380/**2381* Returns the number of flags found in the source code2382*2383* @return int the number of flags found2384*/2385public int getIncludeCount() {2386return numberOfIncludeIfs;2387}23882389/**2390* Returns the fatal error that occured2391*2392* @return PreprocessorWarning the error that occured2393*/2394public PreprocessorWarning getError() {2395return error;2396}23972398/**2399* Adds flags to include when preprocessing.2400*2401* @param flags the flags to be added2402*/2403public void addFlags(Set<String> flags) {2404if (flags == null) {2405throw new IllegalArgumentException();2406}2407this.flags.addAll(flags);2408}24092410/**2411* Sets the flags to include when preprocessing.2412*2413* @param flags the flags to be added2414*/2415public void setFlags(String[] flags) {2416if (flags == null) {2417throw new IllegalArgumentException();2418}2419for (String flag : flags) {2420this.flags.add(flag);2421}2422}24232424/**2425* Sets the required include flags.2426*2427* @param requiredIncludeFlags the required include flags2428*/2429public void setRequiredIncludeFlags(Set<String> requiredIncludeFlags) {2430this.requiredIncludeFlags = requiredIncludeFlags;2431}24322433/**2434* Sets the list to store flags found in this file.2435*2436* @param foundFlags the found flags to be set2437*/2438public void setFoundFlags(List<String> foundFlags) {2439if (foundFlags == null) {2440throw new IllegalArgumentException();2441}2442this.foundFlags = foundFlags;2443}24442445/**2446* Sets the list to store invalid flags found in this file.2447*2448* @param invalidFlags the invalid flags to be set2449*/2450public void setInvalidFlags(List<PreprocessorWarning> invalidFlags) {2451if (invalidFlags == null) {2452throw new IllegalArgumentException();2453}2454this.invalidFlags = invalidFlags;2455}24562457/**2458* Sets the Set to store fixed PRs found in this file.2459*2460* @param fixedPRs the fixed PRs to be stored2461*/2462public void setFixedPRs(Set<String> fixedPRs) {2463if (fixedPRs == null) {2464throw new IllegalArgumentException();2465}2466this.fixedPRs = fixedPRs;2467}24682469/**2470* Sets the Properties to store external messages found in this file.2471*2472* @param externalMessages the external messages2473*/2474public void setExternalMessages(Map<String, String> externalMessages) {2475this.externalMessages = externalMessages;2476}24772478/**2479* Determines whether messages should be inlined.2480*2481* @param inlineMessages the inline messages2482*/2483public void setInlineMessages(boolean inlineMessages) {2484replaceMsg_getString = inlineMessages;2485msgCallInliner = (inlineMessages) ? new MsgCallInliner(false) : null;2486}24872488/**2489* Determines whether message keys should be inlined.2490*2491* @param inlineMessageKeys the inline keys2492*/2493public void setInlineMessageKeys(boolean inlineMessageKeys) {2494replaceMsg_getString = inlineMessageKeys;2495msgCallInliner = (inlineMessageKeys) ? new MsgCallInliner(true) : null;2496}24972498/**2499* Sets the macros names and values for replacement.2500*2501* @param macros the macros2502*/2503public void setMacros(Map<String, String> macros) {2504this.macros.clear();2505this.macros.putAll(macros);2506}25072508/**2509* Sets the Set to store jxelink rules found in this file.2510*2511* @param jxeRules the JXELink rules2512*/2513public void setJxeRules(Set<String> jxeRules) {2514this.jxeRules = jxeRules;2515}25162517/**2518* Determines whether public api should be preserved using jxelink rules.2519*2520* @param jxePreserveApi <code>true</code> if public APIs should be preserved, <code>false</code> otherwise2521*/2522public void setJxePreserveApi(boolean jxePreserveApi) {2523this.jxePreserveApi = jxePreserveApi;2524}25252526/**2527* @param jitAttributes the JIT attributes2528*/2529public void setJitAttributes(JitAttributes jitAttributes) {2530this.jitAttributes = jitAttributes;2531}25322533/**2534* Sets the includeIfUnsure.2535*2536* @param includeIfUnsure the includeIfUnsure to set2537*/2538public void setIncludeIfUnsure(boolean includeIfUnsure) {2539this.includeIfUnsure = includeIfUnsure;2540this.shouldInclude = includeIfUnsure;2541}25422543/**2544* Sets the noWarnIncludeIf.2545*2546* @param noWarnIncludeIf the noWarnIncludeIf to set2547*/2548public void setNoWarnIncludeIf(boolean noWarnIncludeIf) {2549this.noWarnIncludeIf = noWarnIncludeIf;2550}25512552/**2553* Sets the name of the message class that should be used2554*2555* @param name the name of the message class2556*/2557public void setMessageClassName(String name) {2558this.msgClassSubstituter.setMessageClassName(name);2559this.substituteMsgClass = true;2560}25612562}256325642565