FileScope.java
/*
** Module : FileScope.java
** Abstract : Represents the data associated with Progress files from
** the preprocessor perspective, such as file name and arguments.
**
** Copyright (c) 2004-2019, Golden Code Development Corporation.
**
** -#- -I- --Date-- --JPRM-- ---------------------------Description----------------------------
** 001 NVS 20041109 @18763 Created. Initial implementation of a class
** that would represent scope payload objects in
** the preprocessor dictionary.
** 002 NVS 20050207 @19631 This new constructor takes the Progress
** arguments as a string and internally parses
** them into a Map of arguments using a virtual
** include file techninue for a special file
** name '.', which can't be an include file
** reference anyway.
** 003 NVS 20050207 @19632 Changed the pushback buffer depths to 4096.
** 1024 was not enough in one of the files with
** a dynamic RUN statement.
** 004 NVS 20050211 @19769 Replaced the new constructor introduced with
** change #002 with one that takes a filename
** instead of input stream, which makes no sense
** 005 NVS 20050221 @19901 Reworked the constructor again. This time,
** the string of arguments gets tokenized by
** a new instance of TextLexer as multiple
** strings representing positional arguments.
** The change was due to the inability of the
** regular include file syntax to represent the
** empty values.
** 006 NVS 20050512 @21159 Changes forced by changed ClearStream
** constructor. Added fields to track file
** argument usage.
** 007 GES 20070313 @32395 Code formatting cleanup.
** 008 GES 20070403 @32710 Simplified the use of default options.
** 009 GES 20071120 @36194 Added the deferred pushback member.
** 010 GES 20071205 @36215 Added full support for referencing a
** duplicate named argument via its positional
** name and via {*}. See normalizeArguments().
** 011 GES 20080308 @37648 Code cleanup, removal of unneeded code and
** switch to a reader approach for I18N.
** 012 ECF 20101001 Added case-insensitive file search to open()
** method. Necessary for files which originate on a
** case-insensitive file system when processing them
** on a case-sensitive file system.
** 013 GES 20110623 Search entire project for a relative match to an
** include file when the propath search fails. This
** is known as the fallback search. This will make
** the project more tolerant of propath cfg issues.
** 014 GES 20110901 Move to standard warning mechanism.
** 015 OM 20130607 Fixed com.goldencode.p2j.util / Hibernate import conflict.
** 016 CA 20140313 getFile and getFileName need to report the resolved, project-home
** relative name.
** 017 ECF 20150715 Replace StringBuffer with StringBuilder.
** 018 EVL 20160223 Javadoc fixes to make compatible with Oracle Java 8 for Solaris 10.
** 019 HC 20160802 Fixed handling of file separators when including files during
** preprocessing.
** 020 HC 20170110 Fixed a name conflict with antlr.
** 021 HC 20170708 Added source-charset uast preprocessor hint.
** 022 CA 20180410 Some java generic fixes. The allKeyArgs string must double-quote any
** quote char inside the argument's value, as the structure is
** key="val".
** 023 HC 20190227 Fixed hints files not loaded by the preprocessor.
** 024 ECF 20190619 Added include file reference prefix remapping.
*/
/*
** This program is free software: you can redistribute it and/or modify
** it under the terms of the GNU Affero General Public License as
** published by the Free Software Foundation, either version 3 of the
** License, or (at your option) any later version.
**
** This program is distributed in the hope that it will be useful,
** but WITHOUT ANY WARRANTY; without even the implied warranty of
** MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
** GNU Affero General Public License for more details.
**
** You may find a copy of the GNU Affero GPL version 3 at the following
** location: https://www.gnu.org/licenses/agpl-3.0.en.html
**
** Additional terms under GNU Affero GPL version 3 section 7:
**
** Under Section 7 of the GNU Affero GPL version 3, the following additional
** terms apply to the works covered under the License. These additional terms
** are non-permissive additional terms allowed under Section 7 of the GNU
** Affero GPL version 3 and may not be removed by you.
**
** 0. Attribution Requirement.
**
** You must preserve all legal notices or author attributions in the covered
** work or Appropriate Legal Notices displayed by works containing the covered
** work. You may not remove from the covered work any author or developer
** credit already included within the covered work.
**
** 1. No License To Use Trademarks.
**
** This license does not grant any license or rights to use the trademarks
** Golden Code, FWD, any Golden Code or FWD logo, or any other trademarks
** of Golden Code Development Corporation. You are not authorized to use the
** name Golden Code, FWD, or the names of any author or contributor, for
** publicity purposes without written authorization.
**
** 2. No Misrepresentation of Affiliation.
**
** You may not represent yourself as Golden Code Development Corporation or FWD.
**
** You may not represent yourself for publicity purposes as associated with
** Golden Code Development Corporation, FWD, or any author or contributor to
** the covered work, without written authorization.
**
** 3. No Misrepresentation of Source or Origin.
**
** You may not represent the covered work as solely your work. All modified
** versions of the covered work must be marked in a reasonable way to make it
** clear that the modified work is not originating from Golden Code Development
** Corporation or FWD. All modified versions must contain the notices of
** attribution required in this license.
*/
package com.goldencode.p2j.preproc;
import antlr.*;
import com.goldencode.p2j.cfg.*;
import com.goldencode.p2j.util.*;
import com.goldencode.p2j.util.Utils;
import java.nio.charset.*;
import java.util.*;
import java.io.*;
/**
* Represents the data associated with Progress files from the preprocessor
* perspective, such as file name and arguments. An instance is created for
* top level source file (external procedure) as well as for every include
* file that is referenced.
* <p>
* Instances of this class serve scoping levels in a dictionary of scopes.
* <p>
* Preprocessor arguments are stored as mapped data. Both positional and
* named arguments have names. These names are used as map keys. To avoid
* key clashes between positional and named arguments, the names for
* positional arguments are chosen to have a form that is never possible with
* named arguments. Those names are <code>"{N"</code> where N is the argument
* position (from left to right). Simply using names like <code>"N"</code>
* would be insufficient as those can be redefined like this:
* <code>{file &1=value}</code> etc. So, whenever a reference like
* <code>{1}</code> is encountered, it is mapped into the key
* <code>"{1"</code>.
* <p>
* At the time of the instance construction, the special Progress references
* <code>{*}</code> and <code>{&*}</code> are also derived.
*/
public class FileScope
{
/**
* Saved line counter. The line number of the last include file reference
* from this file.
*/
int savedLine = 0;
/**
* Saved column counter. The column number of the last include file
* reference from this file.
*/
int savedColumn = 0;
/**
* Name of the file to preprocess, exactly as specified. Corresponds to
* Progress' <code>{0}</code> argument reference.
*/
private String fileName = null;
/** Reader connected to this file. */
private Reader input = null;
/** Progress arguments to this file. */
private Map<String, String> args = null;
/** All positional arguments for Progress' <code>{*}</code> reference. */
private String allPosArgs = "";
/** All named arguments for Progress' <code>{&*}</code> reference. */
private String allKeyArgs = "";
/** Names of referenced arguments. */
private Set<String> usedArgs = new HashSet<>();
/** <code>true</code> when {*} reference is used. */
private boolean usedAllPos = false;
/** <code>true</code> when {&*} reference is used. */
private boolean usedAllNamed = false;
/** Text to be pushed back onto the stream after this scope is over. */
private String deferred = null;
/** Name of the file found during the fallback search. */
private String fallbackName = null;
/** Track which c'tor was used. */
private boolean explicitInput = false;
/** The charset used for reading input streams. */
private Charset charset = Charset.defaultCharset();
/**
* Wrap an existing input stream for a scope that needs no arguments.
* <p>
* Typically this is only used for the top level source file.
*
* @param fname
* File name of this scope.
* @param input
* The reader to use as the input source.
*/
public FileScope(String fname, Reader input)
{
// save the input stream
this.input = input;
// create input file representations
fileName = fname;
explicitInput = true;
}
/**
* Constructor for an input file with arguments. Typically used for
* include files.
* <p>
* File separators of <code>fname</code> are replaced to match the
* Java runtime's file separators. To recognize file separators of the
* original <code>fname</code> value, the passed in <code>options</code>
* is inspected.
* <p>
* If being called for an included file, any configured filename prefix
* remappings are performed on the name at this time.
* <p>
* Positional arguments are those named in a special way so the names
* never clash: <code>"{N"</code> where N is the relative position from
* left to right (starting at 1).
* <p>
* Named arguments have their regular names as specified in the source
* code of an include file reference.
* <p>
* The first argument in the sequence defines the form of the whole list.
* If its name is <code>"{1"</code>, the list is considered positional,
* otherwise it is named.
* <p>
* A positional list is normalized to only have <code>"{N"</code> names.
* A named list is normalized to only have the original regular names plus
* newly assigned positional names in the form <code>"{N"</code>. This
* enables named arguments to be referenced by a positional reference,
* which is valid in Progress.
* <p>
* To guarantee that the order of the arguments as they appear in the
* <code>{*}</code> and <code>{&*}</code> references is preserved, the
* passed parameter must be an instance of <code>LinkedHashMap</code>.
*
* @param fname
* Source file to be preprocessed. Any <code>UNIX</code> style
* (<code>"/"</code>) file separator characters will be replaced
* with the platform-specific separator character IF DIFFERENT.
* @param args
* Arguments.
* @param options
* The preprocessor options, must not be <code>null</code>.
* @param include
* {@code true} if this object is to be used for an include file reference,
* else {@code false}.
*/
public FileScope(String fname, Map<String, String> args, Options options, boolean include)
{
// create input file representations,
// 4GL on Windows allows \ and / as file separators
if ('/' != File.separatorChar)
{
fname = fname.replace('/', File.separatorChar);
}
// 4GL on Linux/Unix only allows /
if ('\\' != File.separatorChar && !options.isUnixEscapes())
{
fname = fname.replace('\\', File.separatorChar);
}
// apply any include file prefix remappings
Map<String, String> pfxMap = include ? options.getIncludeFilePrefixMap() : null;
if (pfxMap != null && !pfxMap.isEmpty())
{
// check for include file prefix that needs to be replaced/removed;
// note that this will change the fileName instance variable if a prefix is matched
String fileSep = options.getFileSeparator();
boolean caseSens = options.isCaseSensitive();
String fn = caseSens ? fname : fname.toLowerCase();
fn = Utils.replaceSeparator(fn, File.separator, fileSep);
for (Map.Entry<String, String> e : pfxMap.entrySet())
{
String prefix = e.getKey();
if (!caseSens)
{
prefix = prefix.toLowerCase();
}
prefix = Utils.replaceSeparator(prefix, File.separator, fileSep);
if (fn.startsWith(prefix))
{
fname = e.getValue() + fname.substring(prefix.length());
// first match wins
break;
}
}
}
fileName = Configuration.normalizeFilename(fname);
// normalize the arguments
normalizeArguments(args);
}
/**
* Returns the filename as was specified at creation.
*
* @return file name used to construct this object.
*/
public String getFileName()
{
return fileName;
}
/**
* Returns the filename that was found during the fallback attempt.
*
* @return The fallback filename that was dynamically found or
* <code>null</code> if no fallback was attempted or if the
* fallback attempt was not successfull.
*/
public String getFallbackName()
{
return fallbackName;
}
/**
* Returns the embedded File object.
*
* @return <code>File</code> object for this Progress source file.
*/
public File getFile()
{
return new File(explicitInput ? "." : fileName);
}
/**
* Returns the opened stream.
*
* @return Input source or <code>null</code> if closed or never opened.
*/
public Reader getStream()
{
return input;
}
/**
* Returns Progress arguments.
*
* @return set of (name, value) pairs representing Progress arguments
* organized into a <code>Map</code>.
* <code>null</code> if no arguments were given.
*/
public Map<String, String> getArguments()
{
return args;
}
/**
* Copies named Progress arguments into the specified dictionary
* as symbols.
*
* @param dictionary
* symbol dictionary to which the named Progress arguments are
* to be copied
* @return number of named Progress arguments copied.
*/
public int copyNamedArguments(ScopedSymbolDictionary dictionary)
{
if (args == null)
return 0;
Iterator<Map.Entry<String, String>> mi = args.entrySet().iterator();
Map.Entry<String, String> me = null;
String key = null;
String val = null;
while (mi.hasNext())
{
me = mi.next();
key = me.getKey();
val = me.getValue();
dictionary.addSymbol(false, key, new Symbol(Symbol.ARGUMENT, val));
}
return args.size();
}
/**
* Returns a string representing all positional Progress arguments
* matching the Progress' <code>{*}</code> reference.
*
* @return all positional arguments concatenated into a single string.
*/
public String getAllPositionalArguments()
{
return allPosArgs;
}
/**
* Returns a string representing all named Progress arguments
* matching the Progress' <code>{&*}</code> reference.
*
* @return all named arguments concatenated into a single string
* in the form &name="value".
*/
public String getAllNamedArguments()
{
return allKeyArgs;
}
/**
* Resolves the absolute name of the previously specified (relative) file name.
*
* @param paths
* array of strings specifying paths to search for the file
* or <code>null</code>.
* @param env
* Preprocessor environment.
*
* @return <code>true</code> if open succeeded.
*/
public String resolveFileName(String[] paths, Environment env)
{
String fullname = null;
boolean hit = false;
try
{
String fileSep = env.getOpt().getFileSeparator();
File file = new File(fileName);
// locate the file using given paths
if (file.isAbsolute() || paths == null)
{
fullname = Utils.canonicalizePath(fileName, File.separator, fileSep);
if ((new File(fullname)).canRead())
{
hit = true;
}
}
else
{
for (int i = 0; !hit && i < paths.length; i ++)
{
StringBuilder sb = new StringBuilder(paths[i]);
sb.append(File.separator).append(fileName);
String path = sb.toString();
fullname = Utils.canonicalizePath(path, File.separator, fileSep);
if ((new File(fullname)).canRead())
{
hit = true;
}
}
}
}
catch (IOException exc)
{
CharScanner lex = env.getLex();
env.eprint("Error canonicalizing filename '" +
fileName +
"'; " +
exc.getMessage(),
lex.getLine(), lex.getColumn());
}
// special processing for files originating on a case-insensitive file
// system, when processing them on a case-sensitive file system
if (!hit && !env.getOpt().isCaseSensitive())
{
File file = new File(fileName);
// locate the file using given paths
if (file.isAbsolute() || paths == null)
{
fullname = fileName;
File fileci = findCaseInsensitively(fullname, env);
if (fileci != null && fileci.canRead())
{
fullname = fileci.getAbsolutePath();
hit = true;
}
}
else
{
for (int i = 0; !hit && i < paths.length; i ++)
{
StringBuilder sb = new StringBuilder(paths[i]);
sb.append(File.separator).append(fileName);
fullname = sb.toString();
File fileci = findCaseInsensitively(fullname, env);
if (fileci != null && fileci.canRead())
{
fullname = fileci.getAbsolutePath();
hit = true;
}
}
}
}
return hit ? fullname : null;
}
/**
* Opens the input source using the previously specified file.
*
* @param paths
* array of strings specifying paths to search for the file
* or <code>null</code>.
* @param env
* Preprocessor environment.
*
* @return <code>true</code> if open succeeded.
*/
public boolean open(String[] paths, Environment env)
{
// avoid repeated attempts
if (input != null)
{
return true;
}
String fullname = resolveFileName(paths, env);
if (fullname != null)
{
// re-adjust the name to the resolved one
fileName = Configuration.normalizeFilename(fullname);
try
{
input = new BufferedReader(getFileReader(fullname));
}
catch (FileNotFoundException exc)
{
return false;
}
}
return fullname != null;
}
/**
* Attempt a fallback process to find the file and then open the input
* source using the found filename. This is only used when the normal
* <code>open()</code> method fails. Only a unique match will be accepted.
* The search algorithm walks all files under the basepath and if one and
* only one file is found that ends with the filename we are searching
* for, then that match is accepted.
*
* @param env
* Preprocessor environment.
*
* @return <code>true</code> if open succeeded.
*/
public boolean attemptFallback(Environment env)
{
Options options = env.getOpt();
String[] list = options.getFallbackList();
StringBuilder sb = new StringBuilder();
// prepend the file sep so that only complete path segments are matched
sb.append(File.separator).append(fileName);
String target = sb.toString();
if (!options.isCaseSensitive())
target = target.toLowerCase();
int matches = 0;
String found = null;
for (int i = 0; i < list.length && matches < 2; i++)
{
if (list[i].endsWith(target))
{
matches++;
// only ever use the first match
if (matches == 1)
found = list[i];
}
}
// only accept a unique match, otherwise this fails
if (matches == 1)
{
try
{
input = new BufferedReader(getFileReader(found));
fallbackName = found;
}
catch (FileNotFoundException exc)
{
return false;
}
}
return (matches == 1);
}
/**
* Closes the input reader.
*/
public void close()
{
if (input == null)
return;
try
{
input.close();
input = null;
}
catch (IOException e)
{
// ignore
}
}
/**
* Saves the line counter.
*
* @param savedLine
* line number to be saved. Normally stores the current
* line number within this file while an include file
* reference is being processed.
*/
public void setSavedLine(int savedLine)
{
this.savedLine = savedLine;
}
/**
* Gets the saved line counter.
*
* @return saved line counter.
*/
public int getSavedLine()
{
return savedLine;
}
/**
* Saves the column counter.
*
* @param savedColumn
* column number to be saved. Normally stores the current
* column number within this file while an include file
* reference is being processed.
*/
public void setSavedColumn(int savedColumn)
{
this.savedColumn = savedColumn;
}
/**
* Gets the saved column counter.
*
* @return saved column counter.
*/
public int getSavedColumn()
{
return savedColumn;
}
/**
* Marks argument as used.
*
* @param name
* argument name
*/
public void setArgUsed(String name)
{
usedArgs.add(name);
}
/**
* Checks if the argument was used.
*
* @param name
* argument name
* @return <code>true</code> if the argument was used
*/
public boolean isArgUsed(String name)
{
return usedArgs.contains(name);
}
/**
* Marks all positional arguments as used.
*/
public void setAllPosUsed()
{
usedAllPos = true;
}
/**
* Returns "all positional arguments used" flag.
*/
public boolean isAllPosUsed()
{
return usedAllPos;
}
/**
* Marks all named arguments as used.
*/
public void setAllNamedUsed()
{
usedAllNamed = true;
}
/**
* Returns "all named arguments used" flag.
*/
public boolean isAllNamedUsed()
{
return usedAllNamed;
}
/**
* Obtain any text that must be pushed back onto the stream after this
* scope is complete.
*
* @return Deferred pushback text.
*/
public String getDeferredPushback()
{
return deferred;
}
/**
* Set any text that must be pushed back onto the stream after this
* scope is complete.
*
* @param deferred
* Deferred pushback text.
*/
public void setDeferredPushback(String deferred)
{
this.deferred = deferred;
}
/**
* Sets the charset used for reading input streams.
*
* @param charset
* If null, the default JVM charset will be set to the instance.
*/
public void setCharset(Charset charset)
{
this.charset = charset == null ? Charset.defaultCharset() : charset;
}
/**
* Converts this object into a string.
*
* @return a string representation of this object useful for
* diagnostic output.
*/
public String toString()
{
return new String("#" + savedLine + ":" + savedColumn +
fileName + "{" + allPosArgs + "}{" +
allKeyArgs + "}");
}
/**
* Find a file on the file system using a case insensitive matching
* algorithm. Warn (but do not fail) if more than one match is found
* (possible when finding case-insensitively on a case-sensitive file
* system). The order in which files are matched is dependent upon the
* default order in which underlying file system lists files.
*
* @param filename
* Name of file to be found; case of file name and qualifying
* path need not match the case of matching file(s) on the file
* system.
* @param env
* Preprocessor environment (used to emit error/warning messages).
*
* @return The first file whose name is matched case-insensitively to the
* given <code>filename</code>, or <code>null</code> if no match
* is found.
*/
private File findCaseInsensitively(String filename, Environment env)
{
try
{
String fileSep = env.getOpt().getFileSeparator();
List<File> list =
com.goldencode.p2j.util.Utils.getCaseInsensitiveFilenameMatches(filename, fileSep);
int matches = list.size();
if (matches >= 1)
{
// emit warning if we had more than one match
if (matches > 1)
{
CharScanner lex = env.getLex();
String msg = String.format("More than one match for file " +
"'%s'; only first will be used" +
" (%s);",
filename,
list);
env.wprint(msg,
lex == null ? -1 : lex.getLine(),
lex == null ? -1 : lex.getColumn());
}
// if we found it, it exists; no need for an additional check,
// just return it
return list.get(0);
}
}
catch (IOException exc)
{
CharScanner lex = env.getLex();
env.eprint("Error searching for include file \"" +
filename + "\":" + exc.getMessage(),
lex.getLine(), lex.getColumn());
}
return null;
}
/**
* Normalizes the naming of arguments in the given map and store them.
* Create the all positional and all named argument strings.
* <p>
* A positional list is normalized to only have names in the form
* <code>"{N"</code>. Consider this include reference:
* <p>
* <pre>
* {ti.i p1 p2 &a="c" p3 &b=d p4}
* </pre>
* <p>
* In this example, the result would be normalized this way:
* <p>
* <pre>
* Name Value
* ------- -------
* {1 p1
* {2 p2
* {3 a="c"
* {4 p3
* {5 b="d"
* {6 p4
* </pre>
* <p>
* The corresponding <code>{*}</code> reference would be
* <code>"p1 p2 c p3 d p4"</code> and <code>{&*}</code> is empty.
* <p>
* A named list is normalized to only have the original regular
* names plus one newly assigned corresponding <code>"{N"</code> name for
* each argument (including any duplicate named argument). This allows the
* argument values to be referenced by either their names or by their
* relative position such as <code>{&1}</code> for the first named
* argument. Note that duplicate named arguments (arguments where the
* specified name appears more than once in the same argument list) are
* handled as follows:
* <p>
* <ol>
* <li> the value of the first named instance is what is returned when
* the name is referenced (the duplicate value is dropped from the
* list of named arguments)
* <li> all instances can be referenced by their positional name (even
* the duplicated name)
* <li> the {*} reference will include all values (even the values for
* any duplicated name)
* </ol>
* <p>
* For example:
* <p>
* <pre>
* {ti.i &a="c" p1 p2 p3 &b=d p4}
* </pre>
* <p>
* This would produce:
* <p>
* <pre>
* Name Value
* ------- -------
* a c
* b d
* {1 c
* {2 d
* </pre>
* <p>
* The corresponding <code>{&*}</code> reference is
* <code>"&a=c &b=d"</code> and <code>{*}</code> reference is
* <code>"c d"</code>.
* <p>
* To guarantee that the order of the arguments as they appear in the
* <code>{*}</code> and <code>{&*}</code> references is preserved, the
* passed parameter must be an instance of <code>LinkedHashMap</code>.
*
* @param args
* Include file arguments list. This list will store either named
* or positional arguments. Positional arguments are those named
* so the names never clash: "{N" where n is the argument number.
* Named arguments have their explicitly provided name without
* the ampersand. It is not valid to have both except in the
* special case where there is a duplicate named argument. In
* that case there will be a positional argument placeholder so
* that the full positional argument list and the all arguments
* list will contain the value of the duplicated name even though
* the matching named argument never had its value overridden.
* The first argument in the sequence defines the form of the
* whole list. If its name is {1, the list is considered
* positional, otherwise named.
*/
private void normalizeArguments(Map<String, String> args)
{
if (args == null)
return;
StringBuilder allpos = new StringBuilder();
StringBuilder allkey = new StringBuilder();
// create a normalized copy of arguments
this.args = new HashMap<>();
Iterator<Map.Entry<String, String>> mi = args.entrySet().iterator();
Map.Entry<String, String> me = null;
String key = null;
String val = null;
int n = 0;
int m = 0;
boolean named = false;
while (mi.hasNext())
{
boolean duplicate = false;
// fetch next pair
me = mi.next();
n++;
key = me.getKey();
val = me.getValue();
if (val == null)
{
val = "";
}
// determine whether positional or named
if (n == 1)
{
named = !key.startsWith("{");
}
// normalize arguments
if (!named)
{
// rename positional arguments into "{n"
if (!key.startsWith("{"))
key = new String("{" + n);
}
else
{
// detect the duplicate argument placeholder provided by the
// caller
if (key.startsWith("{"))
{
duplicate = true;
}
m++;
}
// insert normalized pair into the table
this.args.put(key, val);
if (named && !duplicate)
{
// add the manufactured positional name unless this is a duplicate
// placeholder (in which case the code above has already handled
// this)
this.args.put("{" + m, val);
// contribute to the {&*} string
if (allkey.length() > 0)
{
allkey.append(" ");
}
allkey.append("&")
.append(key)
.append("=\"")
.append(val.replaceAll("\"", "\"\""))
.append("\"");
}
// contribute to the {*} string
if (allpos.length() > 0)
{
// this must not be quoted. 4GL will expand {*} with the actual values, separated by
// spaces; thus, the {*} at the caller may be different from the {*} at the callee,
// i.e. {some-include.i {*} } and some-include.i's {*} will have different semantics
// and may not match by argument position and value, if any value contains spaces.
allpos.append(" ");
}
allpos.append(val);
}
// create {*} and {&*} references
allPosArgs = allpos.toString();
allKeyArgs = allkey.toString();
}
/**
* Creates a file reader instance with the current charset.
*
* @param fileName
* The file name.
*
* @return a file reader instance
*
* @throws FileNotFoundException
*/
private Reader getFileReader(String fileName)
throws FileNotFoundException
{
return new InputStreamReader(new FileInputStream(fileName), charset);
}
}