/*
* Licensed to the Apache Software Foundation (ASF) under one
* or more contributor license agreements. See the NOTICE file
* distributed with this work for additional information
* regarding copyright ownership. The ASF licenses this file
* to you under the Apache License, Version 2.0 (the
* "License"); you may not use this file except in compliance
* with the License. You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
// Package
///////////////
package jena;
// Imports
///////////////
import static org.apache.jena.atlas.logging.LogCtl.setCmdLogging;
import java.io.OutputStream ;
import java.util.* ;
import java.util.function.BiConsumer;
import org.apache.jena.rdf.model.* ;
import org.apache.jena.rdf.model.impl.RDFWriterFImpl ;
import org.apache.jena.shared.NoWriterForLangException ;
import org.apache.jena.system.JenaSystem ;
import org.apache.jena.util.FileManager ;
import org.apache.jena.util.FileUtils ;
import org.apache.jena.vocabulary.OWL ;
import org.apache.jena.vocabulary.RDFS ;
/**
* <p>
* An RDF utility that takes its name from the Unix utility <em>cat</em>, and
* is used to generate serialisations of the contents of zero or more
* input model serialisations. <strong>Note</strong> In a change from previous
* versions, but to ensure compatability with standard argument handling
* practice, the input language options are <em>no longer sticky</em>. In
* previous versions, <code>rdfcat -n A B C</code> would ensure that A, B
* and C were all read as N3. From Jena 2.5.2 onwards, this requires:
* <code>rdfcat -n A -n B -n C</code>, or the use of the <code>-in</code>
* option.
* </p>
* <p>Synopsis:</p>
* <pre>
* java jena.rdfcat (options|input)*
* where options are:
* -out N3 (aliases n, n3, ttl)
* -out N-TRIPLE (aliases t, ntriple)
* -out RDF/XML (aliases x, rdf, xml, rdfxml)
* -out RDF/XML-ABBREV (default)
* -in N3 (aliases n, n3, ttl)
* -in N-TRIPLE (aliases t, ntriple)
* -in RDF/XML (aliases x, rdf, xml, rdfxml)
* -include
* -noinclude (default)
*
* input is one of:
* -n <filename> for n3 input (aliases -n3, -N3, -ttl)
* -x <filename> for rdf/xml input (aliases -rdf, -xml, -rdfxml)
* -t <filename> for n-triple input (aliases -ntriple)
* or just a URL, a filename, or - for the standard input.
* </pre>
* <p>
* The default
* input language is RDF/XML, but the reader will try to guess the
* input language based on the file extension (e.g. N3 for file with a .n3
* file extension.
* </p>
* <p>The input language options set the language for the following file
* name only. So in the following example, input
* A is read as N3, inputs B, C and D are read as RDF/XML,
* while stdin is read as N-TRIPLE:</p>
* <pre>
* java jena.rdfcat -n A B C -t - -x D
* </pre>
* <p>To change the default input language for all files that do
* not have a specified language encoding, use the <code>-in</code> option.
* </p>
* <p>If the <code>include</code> option is set, the input files are scanned
* for <code>rdfs:seeAlso</code> and <code>owl:imports</code> statements, and
* the objects of these statements are read as well. By default, <code>include</code>
* is off. If <code>include</code> is turned on, the normal behaviour is for
* the including statements (e.g <code>owl:imports</code> to be filtered
* from the output models. To leave such statements in place, use the <code>--nofilter</code>
* option.</p>
* <p>rdfcat uses the Jena {@link org.apache.jena.util.FileManager FileManager}
* to resolve input URI's to locations. This allows, for example, <code>http:</code>
* URI's to be re-directed to local <code>file:</code> locations, to avoid a
* network transaction.</p>
* <p>Examples:</p>
* <pre>
* Join two RDF/XML files together into a single model in RDF/XML-ABBREV:
* java jena.rdfcat in1 in2 > out.rdf
*
* Convert a single RDF/XML file to N3:
* java jena.rdfcat in1 -out N3 > out.n3
*
* Join two owl files one N3, one XML, and their imports, into a single NTRIPLE file:
* java jena.rdfcat -out NTRIPLE -include in1.owl -n in2.owl > out.ntriple
*
* Concatenate two N3-serving http URL's as N-TRIPLE
* java jena.rdfcat -in N3 -out N-TRIPLE http://example.com/a http://example.com/b
* </pre>
* <p>Note that, in a difference from the Unix utility <code>cat</code>, the order
* of input statements is not preserved. The output document is a merge of the
* input documents, and does not preserve any statement ordering from the input
* serialisations. Also, duplicate triples will be suppressed.</p>
*/
@Deprecated
public class rdfcat
{
static { setCmdLogging("jena-log4j.properties") ; }
/** The merged model containing all of the inputs */
protected Model m_model = ModelFactory.createDefaultModel();
/** The output format to write to, defaults to RDF/XML-ABBREV */
protected String m_outputFormat = "RDF/XML-ABBREV";
/** The input format we're expecting for the next URL to be read - defaults to RDF/XML */
protected String m_inputFormat = "RDF/XML";
/** Flag to indicate whether we include owl:imports and rdfs:seeAlso */
protected boolean m_include = false;
/** List of URL's that have been loaded already, occurs check */
protected Set<String> m_seen = new HashSet<>();
/** Flag to control whether import/include statements are filtered from merged models */
protected boolean m_removeIncludeStatements = true;
/** Action queue */
protected List<RCAction> m_actionQ = new ArrayList<>();
// Allow testing to run silent.
public static boolean suppressDeprecationBanner = false ;
// Constants
//////////////////////////////////
/** Argument setting expected input language to N3 */
public final ArgDecl IN_N3 = new ArgDecl( true, "n", "n3", "ttl", "N3",
(arg,val) -> m_actionQ.add( new ReadAction( val, "N3") ) );
/** Argument setting expected input language to RDF/XML */
public final ArgDecl IN_RDF_XML = new ArgDecl( true, "x", "xml", "rdfxml", "rdf",
(arg,val) -> m_actionQ.add( new ReadAction( val, "RDF/XML") ) );
/** Argument setting expected input language to NTRIPLE */
public final ArgDecl IN_NTRIPLE = new ArgDecl( true, "t", "ntriples", "ntriple", "n-triple", "n-triples",
(arg,val) -> m_actionQ.add( new ReadAction( val, "N-TRIPLE" ) ) );
/** Argument to set the output language */
public final ArgDecl OUT_LANG = new ArgDecl( true, "out", (arg,val) -> setOutput( val ) );
/** Argument to set the default input language */
public final ArgDecl IN_LANG = new ArgDecl( true, "in", (arg,val) -> expectInput( val ) );
/** Argument to turn include processing on */
public final ArgDecl INCLUDE = new ArgDecl( false, "include", (arg,val) -> setInclude( true ) );
/** Argument to turn include processing off */
public final ArgDecl NOINCLUDE = new ArgDecl( false, "noinclude", (arg,val) -> setInclude( false ) );
/** Argument to leave import/seeAlso statements in place in flattened models */
public final ArgDecl NOFILTER = new ArgDecl( false, "nofilter", (arg,val) -> setRemoveIncludeStatements( false ) );
/** Argument to show usage */
public final ArgDecl HELP = new ArgDecl( false, "help", (arg,val) -> usage() );
public final ArgDecl USAGE = new ArgDecl( false, "usage", (arg,val) -> usage() );
// Instance variables
//////////////////////////////////
/** The command line processor that handles the arguments */
protected CommandLine m_cmdLine = new RCCommandLine().add( IN_N3 )
.add( IN_NTRIPLE )
.add( IN_RDF_XML )
.add( OUT_LANG )
.add( IN_LANG )
.add( INCLUDE )
.add( NOINCLUDE )
.add( NOFILTER )
.add( HELP )
.add( USAGE );
// Constructors
//////////////////////////////////
// External signature methods
//////////////////////////////////
public static void main( String... args ) {
new rdfcat().go( args );
}
// Internal implementation methods
//////////////////////////////////
/* main loop */
protected void go( String[] args ) {
JenaSystem.init();
if ( ! suppressDeprecationBanner ) {
System.err.println("------------------------------------------------------------------");
System.err.println(" DEPRECATED: Please use 'riot' instead.");
System.err.println(" http://jena.apache.org/documentation/io/#command-line-tools");
System.err.println("------------------------------------------------------------------");
System.err.println() ;
}
m_cmdLine.process( args );
// process any stored items
for (int i = 0; i < m_cmdLine.numItems(); i++) {
m_actionQ.add( new ReadAction( m_cmdLine.getItem( i ), getExpectedInput() ) );
}
for ( RCAction aM_actionQ : m_actionQ )
{
aM_actionQ.run( this );
}
// generate the output
m_model.write( getOutputStream(), m_outputFormat );
}
/** Set the input language of next and subsequent reads */
protected void expectInput( String lang ) {
m_inputFormat = lang;
}
/** Answer the currently expected input format */
protected String getExpectedInput() {
return m_inputFormat;
}
/** Set the language to write the output model in */
protected void setOutput( String lang ) {
m_outputFormat = getCheckedLanguage( lang );
}
/**
Answer the full, checked, language name expanded from <code>shortName</code>.
The shortName is expanded according to the table of abbreviations [below].
It is then checked against RDFWriterFImpl's writer table [this is hacky but
at the moment it's the most available interface] and the NoWriter exception
trapped and replaced by the original IllegalArgument exception.
*/
public static String getCheckedLanguage( String shortLang )
{
String fullLang = unabbreviate.get( shortLang );
String tryLang = (fullLang == null ? shortLang : fullLang);
try { new RDFWriterFImpl().getWriter( tryLang ); }
catch (NoWriterForLangException e)
{ throw new IllegalArgumentException( "'" + shortLang + "' is not recognised as a legal output format" ); }
return tryLang;
}
/**
Map from abbreviated names to full names.
*/
public static Map<String,String> unabbreviate = makeUnabbreviateMap();
/**
Construct the canonical abbreviation map.
*/
protected static Map<String,String> makeUnabbreviateMap()
{
Map<String,String> result = new HashMap<>();
result.put( "x", "RDF/XML" );
result.put( "rdf", "RDF/XML" );
result.put( "rdfxml", "RDF/XML" );
result.put( "xml", "RDF/XML" );
result.put( "n3", "N3" );
result.put( "n", "N3" );
result.put( "ttl", "N3" );
result.put( "ntriples", "N-TRIPLE" );
result.put( "ntriple", "N-TRIPLE" );
result.put( "t", "N-TRIPLE" );
result.put( "owl", "RDF/XML-ABBREV" );
result.put( "abbrev", "RDF/XML-ABBREV" );
return result;
}
/** Set the flag to include owl:imports and rdf:seeAlso files in the output, default off */
protected void setInclude( boolean incl ) {
m_include = incl;
}
/** Set the flag to leave owl:imports and rdfs:seeAlso statements in place, rather than filter them */
protected void setRemoveIncludeStatements( boolean f ) {
m_removeIncludeStatements = f;
}
/* Take the string as an input file or URI, and
* try to read using the current default input syntax.
*/
protected void readInput( String inputName ) {
List<IncludeQueueEntry> queue = new ArrayList<>();
queue.add( new IncludeQueueEntry( inputName, null ) );
while (!queue.isEmpty()) {
IncludeQueueEntry entry = queue.remove( 0 );
String in = entry.m_includeURI;
if (!m_seen.contains( in )) {
m_seen.add( in );
Model inModel = ModelFactory.createDefaultModel();
// check for stdin
if (in.equals( "-" )) {
inModel.read( System.in, null, m_inputFormat );
}
else {
// lang from extension overrides default set on command line
String lang = FileUtils.guessLang( in, m_inputFormat );
FileManager.get().readModel( inModel, in, lang );
}
// check for anything more that we need to read
if (m_include) {
addIncludes( inModel, queue );
}
// merge the models
m_model.add( inModel );
m_model.setNsPrefixes( inModel );
// do we remove the include statement?
if (m_removeIncludeStatements && entry.m_includeStmt != null) {
m_model.remove( entry.m_includeStmt );
}
}
}
}
/** Return the stream to which the output is written, defaults to stdout */
protected OutputStream getOutputStream() {
return System.out;
}
/** Add any additional models to include given the rdfs:seeAlso and
* owl:imports statements in the given model
*/
protected void addIncludes( Model inModel, List<IncludeQueueEntry> queue ) {
// first collect any rdfs:seeAlso statements
StmtIterator i = inModel.listStatements( null, RDFS.seeAlso, (RDFNode) null );
while (i.hasNext()) {
Statement s = i.nextStatement();
queue.add( new IncludeQueueEntry( getURL( s.getObject() ), s ) );
}
// then any owl:imports
i = inModel.listStatements( null, OWL.imports, (RDFNode) null );
while (i.hasNext()) {
Statement s = i.nextStatement();
queue.add( new IncludeQueueEntry( getURL( s.getResource() ), s ) );
}
}
protected void usage() {
System.err.println( "------------------------------------" );
System.err.println( "DEPRECATED: Please use riot instead." );
System.err.println( "------------------------------------\n" );
System.err.println( "Usage: java jena.rdfcat (option|input)*" );
System.err.println( "Concatenates the contents of zero or more input RDF documents." );
System.err.println( "Options: -out N3 | N-TRIPLE | RDF/XML | RDF/XML-ABBREV" );
System.err.println( " -n expect subsequent inputs in N3 syntax" );
System.err.println( " -x expect subsequent inputs in RDF/XML syntax" );
System.err.println( " -t expect subsequent inputs in N-TRIPLE syntax" );
System.err.println( " -[no]include include rdfs:seeAlso and owl:imports" );
System.err.println( "input can be filename, URL, or - for stdin" );
System.err.println( "Recognised aliases for -n are: -n3 -ttl or -N3" );
System.err.println( "Recognised aliases for -x are: -xml -rdf or -rdfxml" );
System.err.println( "Recognised aliases for -t are: -ntriple" );
System.err.println( "Output format aliases: x, xml or rdf for RDF/XML, n, n3 or ttl for N3, t or ntriple for N-TRIPLE" );
System.err.println( "See the Javadoc for jena.rdfcat for additional details." );
System.exit(0);
}
/** Answer a URL string from a resource or literal */
protected String getURL( RDFNode n ) {
return n.isLiteral() ? ((Literal) n).getLexicalForm() : ((Resource) n).getURI();
}
//==============================================================================
// Inner class definitions
//==============================================================================
/** Local extension to CommandLine to handle mixed arguments and values */
protected class RCCommandLine
extends CommandLine
{
/** Don't stop processing args on the first non-arg */
public boolean xendProcessing( String argStr ) {
return false;
}
/** Handle an unrecognised argument by assuming it's a URI to read */
@Override
public void handleUnrecognizedArg( String argStr ) {
if (argStr.equals("-") || !argStr.startsWith( "-" )) {
// queue this action for reading later
m_actionQ.add( new ReadAction( argStr, getExpectedInput() ) );
}
else {
System.err.println( "Unrecognised argument: " + argStr );
usage();
}
}
/** Hook to test whether this argument should be processed further
*/
@Override
public boolean ignoreArgument( String argStr ) {
return !argStr.startsWith("-") || argStr.length() == 1;
}
/** Answer an iterator over the non-arg items from the command line */
public Iterator<String> getItems() {
return items.iterator();
}
}
/** Queue entry that contains both a URI to be included, and a statement that may be removed */
protected class IncludeQueueEntry
{
protected String m_includeURI;
protected Statement m_includeStmt;
protected IncludeQueueEntry( String includeURI, Statement includeStmt ) {
m_includeURI = includeURI;
m_includeStmt = includeStmt;
}
}
/** Simple action object for local processing queue */
protected interface RCAction {
public void run( rdfcat rc );
}
/** Action to set the output format */
protected class ReadAction
implements RCAction
{
private String m_lang;
private String m_uri;
protected ReadAction( String uri, String lang ) {
m_lang = lang;
m_uri = uri;
}
/** perform the action of reading a uri */
@Override
public void run( rdfcat rc ) {
String l = rc.getExpectedInput();
if (m_lang != null) {
// if an input lang was given, use that
rc.expectInput( m_lang );
}
rc.readInput( m_uri );
// put the lang back to default
rc.expectInput( l );
}
}
/**
* Command line argument processing based on a trigger model.
* An action is called whenever an argument is encountered. Example:
* <CODE>
* public static void main (String[] args)
* {
* CommandLine cl = new CommandLine() ;
* cl.add(false, "verbose")
* .add(true, "--file") ;
* cl.process(args) ;
*
* for ( Iterator iter = cl.args() ; iter.hasNext() ; )
* ...
* }
* </CODE>
* A gloabl hook is provided to inspect arguments just before the
* action. Tracing is enabled by setting this to a suitable function
* such as that provided by trace():
* <CODE>
* cl.setHook(cl.trace()) ;
* </CODE>
*
* <ul>
* <li>Neutral as to whether options have - or --</li>
* <li>Does not allow multiple single letter options to be concatenated.</li>
* <li>Options may be ended with - or --</li>
* <li>Arguments with values can use "="</li>
* </ul>
*/
static class CommandLine
{
/* Extra processor called before the registered one when set.
* Used for tracing.
*/
protected BiConsumer<String,String> argHook = null ;
protected String usage = null ;
protected Map<String, ArgDecl> argMap = new HashMap<>() ;
protected Map<String, Arg> args = new HashMap<>() ;
//protected boolean ignoreUnknown = false ;
// Rest of the items found on the command line
String indirectionMarker = "@" ;
protected boolean allowItemIndirect = false ; // Allow @ to mean contents of file
boolean ignoreIndirectionMarker = false ; // Allow comand line items to have leading @ but strip it.
protected List<String> items = new ArrayList<>() ;
/** Creates new CommandLine */
public CommandLine()
{
}
/** Set the global argument handler. Called on every valid argument.
* @param argHandler Handler
*/
public void setHook(BiConsumer<String, String> argHandler) { argHook = argHandler ; }
public void setUsage(String usageMessage) { usage = usageMessage ; }
public boolean hasArgs() { return args.size() > 0 ; }
public boolean hasItems() { return items.size() > 0 ; }
public Iterator<Arg> args() { return args.values().iterator() ; }
// public Map args() { return args ; }
// public List items() { return items ; }
public int numArgs() { return args.size() ; }
public int numItems() { return items.size() ; }
public void pushItem(String s) { items.add(s) ; }
public boolean isIndirectItem(int i)
{ return allowItemIndirect && items.get(i).startsWith(indirectionMarker) ; }
public String getItem(int i)
{
return getItem(i, allowItemIndirect) ;
}
public String getItem(int i, boolean withIndirect)
{
if ( i < 0 || i >= items.size() )
return null ;
String item = items.get(i) ;
if ( withIndirect && item.startsWith(indirectionMarker) )
{
item = item.substring(1) ;
try { item = FileUtils.readWholeFileAsUTF8(item) ; }
catch (Exception ex)
{ throw new IllegalArgumentException("Failed to read '"+item+"': "+ex.getMessage()) ; }
}
return item ;
}
/** Process a set of command line arguments.
* @param argv The words of the command line.
* @throws IllegalArgumentException Throw when something is wrong (no value found, action fails).
*/
public void process(String[] argv) throws java.lang.IllegalArgumentException
{
List<String> argList = new ArrayList<>() ;
argList.addAll(Arrays.asList(argv)) ;
int i = 0 ;
for ( ; i < argList.size() ; i++ )
{
String argStr = argList.get(i) ;
if (endProcessing(argStr))
break ;
if ( ignoreArgument(argStr) )
continue ;
// If the flag has a "=" or :, it is long form --arg=value.
// Split and insert the arg
int j1 = argStr.indexOf('=') ;
int j2 = argStr.indexOf(':') ;
int j = Integer.MAX_VALUE ;
if ( j1 > 0 && j1 < j )
j = j1 ;
if ( j2 > 0 && j2 < j )
j = j2 ;
if ( j != Integer.MAX_VALUE )
{
String a2 = argStr.substring(j+1) ;
argList.add(i+1,a2) ;
argStr = argStr.substring(0,j) ;
}
argStr = ArgDecl.canonicalForm(argStr) ;
String val = null ;
if ( argMap.containsKey(argStr) )
{
if ( ! args.containsKey(argStr))
args.put(argStr, new Arg(argStr)) ;
Arg arg = args.get(argStr) ;
ArgDecl argDecl = argMap.get(argStr) ;
if ( argDecl.takesValue() )
{
if ( i == (argList.size()-1) )
throw new IllegalArgumentException("No value for argument: "+arg.getName()) ;
i++ ;
val = argList.get(i) ;
arg.setValue(val) ;
arg.addValue(val) ;
}
// Global hook
if ( argHook != null )
argHook.accept(argStr, val) ;
argDecl.trigger(arg) ;
}
else
handleUnrecognizedArg( argList.get(i) );
// if ( ! getIgnoreUnknown() )
// // Not recognized
// throw new IllegalArgumentException("Unknown argument: "+argStr) ;
}
// Remainder.
if ( i < argList.size() )
{
if ( argList.get(i).equals("-") || argList.get(i).equals("--") )
i++ ;
for ( ; i < argList.size() ; i++ )
{
String item = argList.get(i) ;
items.add(item) ;
}
}
}
/** Hook to test whether this argument should be processed further
*/
public boolean ignoreArgument( String argStr )
{ return false ; }
/** Answer true if this argument terminates argument processing for the rest
* of the command line. Default is to stop just before the first arg that
* does not start with "-", or is "-" or "--".
*/
public boolean endProcessing( String argStr )
{
return ! argStr.startsWith("-") || argStr.equals("--") || argStr.equals("-");
}
/**
* Handle an unrecognised argument; default is to throw an exception
* @param argStr The string image of the unrecognised argument
*/
public void handleUnrecognizedArg( String argStr ) {
throw new IllegalArgumentException("Unknown argument: "+argStr) ;
}
/** Test whether an argument was seen.
*/
public boolean contains(ArgDecl argDecl) { return getArg(argDecl) != null ; }
/** Test whether an argument was seen.
*/
public boolean contains(String s) { return getArg(s) != null ; }
/** Test whether the command line had a particular argument
*
* @param argName
*/
public boolean hasArg(String argName) { return getArg(argName) != null ; }
/** Test whether the command line had a particular argument
*
* @param argDecl
*/
public boolean hasArg(ArgDecl argDecl) { return getArg(argDecl) != null ; }
/** Get the argument associated with the argument declaration.
* Actually returns the LAST one seen
* @param argDecl Argument declaration to find
* @return Last argument that matched.
*/
public Arg getArg(ArgDecl argDecl)
{
Arg arg = null ;
for ( Arg a : args.values() )
{
if ( argDecl.matches( a ) )
{
arg = a;
}
}
return arg ;
}
/** Get the argument associated with the arguement name.
* Actually returns the LAST one seen
* @param arg Argument declaration to find
* @return Arg - Last argument that matched.
*/
public Arg getArg(String arg)
{
arg = ArgDecl.canonicalForm(arg) ;
return args.get(arg) ;
}
/**
* Returns the value (a string) for an argument with a value -
* returns null for no argument and no value.
* @param argDecl
* @return String
*/
public String getValue(ArgDecl argDecl)
{
Arg arg = getArg(argDecl) ;
if ( arg == null )
return null ;
if ( arg.hasValue())
return arg.getValue() ;
return null ;
}
/**
* Returns the value (a string) for an argument with a value -
* returns null for no argument and no value.
* @param argName
* @return String
*/
public String getValue(String argName)
{
Arg arg = getArg(argName) ;
if ( arg == null )
return null ;
return arg.getValue() ;
}
/**
* Returns all the values (0 or more strings) for an argument.
* @param argDecl
* @return List
*/
public List<String> getValues(ArgDecl argDecl)
{
Arg arg = getArg(argDecl) ;
if ( arg == null )
return null ;
return arg.getValues() ;
}
/**
* Returns all the values (0 or more strings) for an argument.
* @param argName
* @return List
*/
public List<String> getValues(String argName)
{
Arg arg = getArg(argName) ;
if ( arg == null )
return null ;
return arg.getValues() ;
}
/** Add an argument to those to be accepted on the command line.
* @param argName Name
* @param hasValue True if the command takes a (string) value
* @return The CommandLine processor object
*/
public CommandLine add(String argName, boolean hasValue)
{
return add(new ArgDecl(hasValue, argName)) ;
}
/** Add an argument to those to be accepted on the command line.
* Argument order reflects ArgDecl.
* @param hasValue True if the command takes a (string) value
* @param argName Name
* @return The CommandLine processor object
*/
public CommandLine add(boolean hasValue, String argName)
{
return add(new ArgDecl(hasValue, argName)) ;
}
/** Add an argument object
* @param arg Argument to add
* @return The CommandLine processor object
*/
public CommandLine add(ArgDecl arg)
{
for ( Iterator<String> iter = arg.names() ; iter.hasNext() ; )
argMap.put(iter.next(), arg) ;
return this ;
}
// public boolean getIgnoreUnknown() { return ignoreUnknown ; }
// public void setIgnoreUnknown(boolean ign) { ignoreUnknown = ign ; }
/**
* @return Returns whether items starting "@" have the value of named file.
*/
public boolean allowItemIndirect()
{
return allowItemIndirect ;
}
/**
* @param allowItemIndirect Set whether items starting "@" have the value of named file.
*/
public void setAllowItemIndirect(boolean allowItemIndirect)
{
this.allowItemIndirect = allowItemIndirect ;
}
/**
* @return Returns the ignoreIndirectionMarker.
*/
public boolean isIgnoreIndirectionMarker()
{
return ignoreIndirectionMarker ;
}
/**
* @return Returns the indirectionMarker.
*/
public String getIndirectionMarker()
{
return indirectionMarker ;
}
/**
* @param indirectionMarker The indirectionMarker to set.
*/
public void setIndirectionMarker(String indirectionMarker)
{
this.indirectionMarker = indirectionMarker ;
}
/**
* @param ignoreIndirectionMarker The ignoreIndirectionMarker to set.
*/
public void setIgnoreIndirectionMarker(boolean ignoreIndirectionMarker)
{
this.ignoreIndirectionMarker = ignoreIndirectionMarker ;
}
public BiConsumer<String, String> trace() {
return (arg, val) -> {
System.err.println("Seen: " + arg + (val != null ? " = " + val : ""));
};
}
}
/** A command line argument that has been foundspecification.
*/
static class Arg
{
String name ;
String value ;
List<String> values = new ArrayList<>() ;
Arg() { name = null ; value = null ; }
Arg(String _name) { this() ; setName(_name) ; }
Arg(String _name, String _value) { this() ; setName(_name) ; setValue(_value) ; }
void setName(String n) { name = n ; }
void setValue(String v) { value = v ; }
void addValue(String v) { values.add(v) ; }
public String getName() { return name ; }
public String getValue() { return value; }
public List<String> getValues() { return values; }
public boolean hasValue() { return value != null ; }
public boolean matches(ArgDecl decl)
{
return decl.getNames().contains(name) ;
}
}
/** A command line argument specification.
*/
static class ArgDecl
{
boolean takesValue ;
Set<String> names = new HashSet<>() ;
boolean takesArg = false ;
List<BiConsumer<String, String>> argHooks = new ArrayList<>() ;
public static final boolean HasValue = true ;
public static final boolean NoValue = false ;
/** Create a declaration for a command argument.
*
* @param hasValue Does it take a value or not?
*/
public ArgDecl(boolean hasValue)
{
takesValue = hasValue ;
}
/** Create a declaration for a command argument.
*
* @param hasValue Does it take a value or not?
* @param name Name of argument
*/
public ArgDecl(boolean hasValue, String name)
{
this(hasValue) ;
addName(name) ;
}
/** Create a declaration for a command argument.
*
* @param hasValue Does it take a value or not?
* @param name Name of argument
* @param handler BiConsumer<String, String>
*/
public ArgDecl(boolean hasValue, String name, BiConsumer<String, String> handler)
{
this(hasValue) ;
addName(name) ;
addHook( handler );
}
/** Create a declaration for a command argument.
*
* @param hasValue Does it take a value or not?
* @param name1 Name of argument
* @param name2 Name of argument
*/
public ArgDecl(boolean hasValue, String name1, String name2)
{
this(hasValue) ;
addName(name1) ;
addName(name2) ;
}
/** Create a declaration for a command argument.
*
* @param hasValue Does it take a value or not?
* @param name1 Name of argument
* @param name2 Name of argument
* @param handler BiConsumer<String, String>
*/
public ArgDecl(boolean hasValue, String name1, String name2, BiConsumer<String, String> handler)
{
this(hasValue) ;
addName(name1) ;
addName(name2) ;
addHook( handler );
}
/** Create a declaration for a command argument.
*
* @param hasValue Does it take a value or not?
* @param name1 Name of argument
* @param name2 Name of argument
* @param name3 Name of argument
*/
public ArgDecl(boolean hasValue, String name1, String name2, String name3)
{
this(hasValue) ;
addName(name1) ;
addName(name2) ;
addName(name3) ;
}
/** Create a declaration for a command argument.
*
* @param hasValue Does it take a value or not?
* @param name1 Name of argument
* @param name2 Name of argument
* @param name3 Name of argument
* @param handler BiConsumer<String, String>
*/
public ArgDecl(boolean hasValue, String name1, String name2, String name3, BiConsumer<String, String> handler)
{
this(hasValue) ;
addName(name1) ;
addName(name2) ;
addName(name3) ;
addHook( handler );
}
/** Create a declaration for a command argument.
*
* @param hasValue Does it take a value or not?
* @param name1 Name of argument
* @param name2 Name of argument
* @param name3 Name of argument
* @param name4 Name of argument
*/
public ArgDecl(boolean hasValue, String name1, String name2, String name3, String name4)
{
this(hasValue) ;
addName(name1) ;
addName(name2) ;
addName(name3) ;
addName(name4) ;
}
/** Create a declaration for a command argument.
*
* @param hasValue Does it take a value or not?
* @param name1 Name of argument
* @param name2 Name of argument
* @param name3 Name of argument
* @param name4 Name of argument
* @param handler BiConsumer<String, String>
*/
public ArgDecl(boolean hasValue, String name1, String name2, String name3, String name4, BiConsumer<String, String> handler)
{
this(hasValue) ;
addName(name1) ;
addName(name2) ;
addName(name3) ;
addName(name4) ;
addHook( handler );
}
/** Create a declaration for a command argument.
*
* @param hasValue Does it take a value or not?
* @param name1 Name of argument
* @param name2 Name of argument
* @param name3 Name of argument
* @param name4 Name of argument
* @param name5 Name of argument
* @param handler BiConsumer<String, String>
*/
public ArgDecl(boolean hasValue, String name1, String name2, String name3, String name4, String name5, BiConsumer<String, String> handler)
{
this(hasValue) ;
addName(name1) ;
addName(name2) ;
addName(name3) ;
addName(name4) ;
addName(name5) ;
addHook( handler );
}
public void addName(String name)
{
name = canonicalForm(name) ;
names.add(name) ;
}
public Set<String> getNames() { return names ; }
public Iterator<String> names() { return names.iterator() ; }
// Callback model
public void addHook(BiConsumer<String, String> argHandler)
{
argHooks.add(argHandler) ;
}
protected void trigger(Arg arg)
{
argHooks.forEach(action -> action.accept( arg.getName(), arg.getValue() ));
}
public boolean takesValue() { return takesValue ; }
public boolean matches(Arg a)
{
for ( String n : names )
{
if ( a.getName().equals( n ) )
{
return true;
}
}
return false ;
}
public boolean matches(String arg)
{
arg = canonicalForm(arg) ;
return names.contains(arg) ;
}
static String canonicalForm(String str)
{
if ( str.startsWith("--") )
return str.substring(2) ;
if ( str.startsWith("-") )
return str.substring(1) ;
return str ;
}
}
}