From 3d024a271aa4cef481143edaa8edba2df6b481a9 Mon Sep 17 00:00:00 2001 From: Rob Vesse Date: Fri, 25 Nov 2016 14:48:33 +0000 Subject: [PATCH] Initial work on improved error handling (#53) Adds a new ParserErrorHandler interface and incorporates it into the ParserMetadata. Doesn't yet utilise it properly --- .../rvesse/airline/annotations/Parser.java | 20 ++++-- .../rvesse/airline/builder/ParserBuilder.java | 17 ++++- .../rvesse/airline/model/MetadataLoader.java | 6 ++ .../rvesse/airline/model/ParserMetadata.java | 21 +++++- .../rvesse/airline/parser/ParseResult.java | 71 +++++++++++++++++++ .../handlers/AbstractCollectingHandler.java | 44 ++++++++++++ .../parser/errors/handlers/CollectAll.java | 36 ++++++++++ .../parser/errors/handlers/FailAll.java | 42 +++++++++++ .../parser/errors/handlers/FailFast.java | 33 +++++++++ .../errors/handlers/ParserErrorHandler.java | 46 ++++++++++++ 10 files changed, 326 insertions(+), 10 deletions(-) create mode 100644 airline-core/src/main/java/com/github/rvesse/airline/parser/ParseResult.java create mode 100644 airline-core/src/main/java/com/github/rvesse/airline/parser/errors/handlers/AbstractCollectingHandler.java create mode 100644 airline-core/src/main/java/com/github/rvesse/airline/parser/errors/handlers/CollectAll.java create mode 100644 airline-core/src/main/java/com/github/rvesse/airline/parser/errors/handlers/FailAll.java create mode 100644 airline-core/src/main/java/com/github/rvesse/airline/parser/errors/handlers/FailFast.java create mode 100644 airline-core/src/main/java/com/github/rvesse/airline/parser/errors/handlers/ParserErrorHandler.java diff --git a/airline-core/src/main/java/com/github/rvesse/airline/annotations/Parser.java b/airline-core/src/main/java/com/github/rvesse/airline/annotations/Parser.java index b68866efb..b919406f8 100644 --- a/airline-core/src/main/java/com/github/rvesse/airline/annotations/Parser.java +++ b/airline-core/src/main/java/com/github/rvesse/airline/annotations/Parser.java @@ -28,6 +28,8 @@ import com.github.rvesse.airline.SingleCommand; import com.github.rvesse.airline.TypeConverter; import com.github.rvesse.airline.model.ParserMetadata; +import com.github.rvesse.airline.parser.errors.handlers.FailFast; +import com.github.rvesse.airline.parser.errors.handlers.ParserErrorHandler; import com.github.rvesse.airline.parser.options.OptionParser; /** @@ -109,7 +111,7 @@ * * @return Command aliases */ - Alias[]aliases() default {}; + Alias[] aliases() default {}; /** * Defines the name of a file from which user defined command aliases should @@ -134,7 +136,7 @@ * * @return */ - String[]userAliasesSearchLocation() default ""; + String[] userAliasesSearchLocation() default ""; /** * Sets the prefix used for properties that define aliases @@ -172,7 +174,7 @@ * @return Option parser classes */ @SuppressWarnings("rawtypes") - Class[]optionParsers() default {}; + Class[] optionParsers() default {}; /** * Sets the command factory class to use @@ -180,12 +182,20 @@ * @return Command factory class */ @SuppressWarnings("rawtypes") - ClasscommandFactory() default DefaultCommandFactory.class; + Class commandFactory() default DefaultCommandFactory.class; /** * Sets the type converter class to use * * @return Type converter class */ - ClasstypeConverter() default DefaultTypeConverter.class; + Class typeConverter() default DefaultTypeConverter.class; + + /** + * Sets the error handler to use, defaults to {@code FailFast} which throws + * errors as soon as they are encountered + * + * @return Error handler to use + */ + Class errorHandler() default FailFast.class; } diff --git a/airline-core/src/main/java/com/github/rvesse/airline/builder/ParserBuilder.java b/airline-core/src/main/java/com/github/rvesse/airline/builder/ParserBuilder.java index 5d1ce5429..2c5afdc7d 100644 --- a/airline-core/src/main/java/com/github/rvesse/airline/builder/ParserBuilder.java +++ b/airline-core/src/main/java/com/github/rvesse/airline/builder/ParserBuilder.java @@ -27,6 +27,7 @@ import com.github.rvesse.airline.model.AliasMetadata; import com.github.rvesse.airline.model.ParserMetadata; import com.github.rvesse.airline.parser.aliases.UserAliasesSource; +import com.github.rvesse.airline.parser.errors.handlers.ParserErrorHandler; import com.github.rvesse.airline.parser.options.ClassicGetOptParser; import com.github.rvesse.airline.parser.options.LongGetOptParser; import com.github.rvesse.airline.parser.options.OptionParser; @@ -47,6 +48,7 @@ public class ParserBuilder extends AbstractBuilder> { protected final List> optionParsers = new ArrayList<>(); protected String argsSeparator; protected UserAliasesSource userAliases; + protected ParserErrorHandler errorHandler; public static ParserMetadata defaultConfiguration() { return new ParserBuilder().build(); @@ -217,6 +219,16 @@ public ParserBuilder withDefaultTypeConverter() { return this; } + public ParserBuilder withErrorHandler(ParserErrorHandler errorHandler) { + this.errorHandler = errorHandler; + return this; + } + + public ParserBuilder withDefaultErrorHandler() { + this.errorHandler = null; + return this; + } + /** * Configures the CLI to use the given option parser *

@@ -344,7 +356,8 @@ public ParserMetadata build() { aliasData = new ArrayList<>(); } - return new ParserMetadata(commandFactory, optionParsers, typeConverter, allowAbbreviatedCommands, - allowAbbreviatedOptions, aliasData, userAliases, aliasesOverrideBuiltIns, aliasesMayChain, argsSeparator); + return new ParserMetadata(commandFactory, optionParsers, typeConverter, errorHandler, + allowAbbreviatedCommands, allowAbbreviatedOptions, aliasData, userAliases, aliasesOverrideBuiltIns, + aliasesMayChain, argsSeparator); } } \ No newline at end of file diff --git a/airline-core/src/main/java/com/github/rvesse/airline/model/MetadataLoader.java b/airline-core/src/main/java/com/github/rvesse/airline/model/MetadataLoader.java index c29a2e7f6..32ac56bf0 100644 --- a/airline-core/src/main/java/com/github/rvesse/airline/model/MetadataLoader.java +++ b/airline-core/src/main/java/com/github/rvesse/airline/model/MetadataLoader.java @@ -32,6 +32,7 @@ import com.github.rvesse.airline.help.sections.factories.HelpSectionRegistry; import com.github.rvesse.airline.help.suggester.Suggester; import com.github.rvesse.airline.parser.ParserUtil; +import com.github.rvesse.airline.parser.errors.handlers.FailFast; import com.github.rvesse.airline.parser.options.OptionParser; import com.github.rvesse.airline.restrictions.ArgumentsRestriction; import com.github.rvesse.airline.restrictions.GlobalRestriction; @@ -88,6 +89,11 @@ private static ParserMetadata loadParser(Parser parserConfig) { } else { builder = builder.withDefaultCommandFactory(); } + if (!parserConfig.errorHandler().equals(FailFast.class)) { + builder = builder.withErrorHandler(ParserUtil.createInstance(parserConfig.errorHandler())); + } else { + builder = builder.withDefaultErrorHandler(); + } // Abbreviation options if (parserConfig.allowCommandAbbreviation()) { diff --git a/airline-core/src/main/java/com/github/rvesse/airline/model/ParserMetadata.java b/airline-core/src/main/java/com/github/rvesse/airline/model/ParserMetadata.java index 90271f773..d83b86abe 100644 --- a/airline-core/src/main/java/com/github/rvesse/airline/model/ParserMetadata.java +++ b/airline-core/src/main/java/com/github/rvesse/airline/model/ParserMetadata.java @@ -24,6 +24,8 @@ import com.github.rvesse.airline.TypeConverter; import com.github.rvesse.airline.DefaultTypeConverter; import com.github.rvesse.airline.parser.aliases.UserAliasesSource; +import com.github.rvesse.airline.parser.errors.handlers.FailFast; +import com.github.rvesse.airline.parser.errors.handlers.ParserErrorHandler; import com.github.rvesse.airline.parser.options.OptionParser; import com.github.rvesse.airline.utils.AirlineUtils; @@ -44,16 +46,20 @@ public class ParserMetadata { private final TypeConverter typeConverter; private final CommandFactory commandFactory; private final String argsSeparator; + private final ParserErrorHandler errorHandler; public ParserMetadata(CommandFactory commandFactory, List> optionParsers, - TypeConverter typeConverter, boolean allowAbbreviateCommands, boolean allowAbbreviatedOptions, - List aliases, UserAliasesSource userAliases, boolean aliasesOverrideBuiltIns, - boolean aliasesMayChain, String argumentsSeparator) { + TypeConverter typeConverter, ParserErrorHandler errorHandler, boolean allowAbbreviateCommands, + boolean allowAbbreviatedOptions, List aliases, UserAliasesSource userAliases, + boolean aliasesOverrideBuiltIns, boolean aliasesMayChain, String argumentsSeparator) { if (optionParsers == null) throw new NullPointerException("optionParsers cannot be null"); if (aliases == null) throw new NullPointerException("aliases cannot be null"); + // Error handling + this.errorHandler = errorHandler != null ? errorHandler : new FailFast(); + // Command parsing this.commandFactory = commandFactory != null ? commandFactory : new DefaultCommandFactory(); this.allowAbbreviatedCommands = allowAbbreviateCommands; @@ -97,6 +103,15 @@ public TypeConverter getTypeConverter() { return typeConverter; } + /** + * Gets the error handler to use + * + * @return Error handler + */ + public ParserErrorHandler getErrorHandler() { + return errorHandler; + } + /** * Gets the defined command aliases * diff --git a/airline-core/src/main/java/com/github/rvesse/airline/parser/ParseResult.java b/airline-core/src/main/java/com/github/rvesse/airline/parser/ParseResult.java new file mode 100644 index 000000000..cb053cc2a --- /dev/null +++ b/airline-core/src/main/java/com/github/rvesse/airline/parser/ParseResult.java @@ -0,0 +1,71 @@ +/** + * Copyright (C) 2010-16 the original author or authors. + * + * Licensed 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 com.github.rvesse.airline.parser; + +import java.util.Collection; +import java.util.Collections; + +import com.github.rvesse.airline.parser.errors.ParseException; + +/** + * Represents parsing results + * + * @author rvesse + * + * @param + * Command type + */ +public class ParseResult { + private final ParseState state; + private final Collection errors; + + public ParseResult(ParseState state, Collection errors) { + if (state == null) + throw new NullPointerException("state cannot be null"); + this.state = state; + this.errors = errors != null ? Collections. unmodifiableCollection(errors) + : Collections. emptyList(); + } + + /** + * Indicates whether parsing was successful + * + * @return True if successful, false if any errors occurred + */ + public boolean wasSuccessful() { + return this.errors.size() == 0; + } + + /** + * Gets the final parser state + * + * @return Parser state + */ + public ParseState getState() { + return this.state; + } + + /** + * Gets the collection of errors that occurred, may be empty if parsing was + * successful + * + * @return Errors + */ + public Collection getErrors() { + return this.errors; + } +} diff --git a/airline-core/src/main/java/com/github/rvesse/airline/parser/errors/handlers/AbstractCollectingHandler.java b/airline-core/src/main/java/com/github/rvesse/airline/parser/errors/handlers/AbstractCollectingHandler.java new file mode 100644 index 000000000..5ef106653 --- /dev/null +++ b/airline-core/src/main/java/com/github/rvesse/airline/parser/errors/handlers/AbstractCollectingHandler.java @@ -0,0 +1,44 @@ +/** + * Copyright (C) 2010-16 the original author or authors. + * + * Licensed 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 com.github.rvesse.airline.parser.errors.handlers; + +import java.util.ArrayList; +import java.util.List; + +import com.github.rvesse.airline.parser.errors.ParseException; + +public abstract class AbstractCollectingHandler implements ParserErrorHandler { + + protected List errors = new ArrayList<>(); + + public AbstractCollectingHandler() { + super(); + } + + @Override + public void handleError(ParseException e) { + this.errors.add(e); + } + + protected List getCollection() { + return this.errors; + } + + protected void resetCollection() { + this.errors = new ArrayList<>(); + } + +} \ No newline at end of file diff --git a/airline-core/src/main/java/com/github/rvesse/airline/parser/errors/handlers/CollectAll.java b/airline-core/src/main/java/com/github/rvesse/airline/parser/errors/handlers/CollectAll.java new file mode 100644 index 000000000..2999bd7f5 --- /dev/null +++ b/airline-core/src/main/java/com/github/rvesse/airline/parser/errors/handlers/CollectAll.java @@ -0,0 +1,36 @@ +/** + * Copyright (C) 2010-16 the original author or authors. + * + * Licensed 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 com.github.rvesse.airline.parser.errors.handlers; + +import com.github.rvesse.airline.parser.ParseResult; +import com.github.rvesse.airline.parser.ParseState; + +/** + * Error handler which collects all the errors for later inspection + * + * @author rvesse + * + */ +public class CollectAll extends AbstractCollectingHandler { + + @Override + public ParseResult finished(ParseState state) { + ParseResult result = new ParseResult<>(state, getCollection()); + resetCollection(); + return result; + } + +} diff --git a/airline-core/src/main/java/com/github/rvesse/airline/parser/errors/handlers/FailAll.java b/airline-core/src/main/java/com/github/rvesse/airline/parser/errors/handlers/FailAll.java new file mode 100644 index 000000000..1ac65f93f --- /dev/null +++ b/airline-core/src/main/java/com/github/rvesse/airline/parser/errors/handlers/FailAll.java @@ -0,0 +1,42 @@ +/** + * Copyright (C) 2010-16 the original author or authors. + * + * Licensed 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 com.github.rvesse.airline.parser.errors.handlers; + +import java.util.Collection; + +import com.github.rvesse.airline.parser.ParseResult; +import com.github.rvesse.airline.parser.ParseState; +import com.github.rvesse.airline.parser.errors.ParseException; + +public class FailAll extends AbstractCollectingHandler { + + @Override + public ParseResult finished(ParseState state) { + Collection errors = getCollection(); + if (errors.size() == 1) { + throw errors.iterator().next(); + } else if (errors.size() > 1) { + ParseException aggEx = new ParseException("Parsing encountered %d error(s)", errors.size()); + for (ParseException e : errors) { + aggEx.addSuppressed(e); + } + throw aggEx; + } else { + return new ParseResult<>(state, null); + } + } + +} diff --git a/airline-core/src/main/java/com/github/rvesse/airline/parser/errors/handlers/FailFast.java b/airline-core/src/main/java/com/github/rvesse/airline/parser/errors/handlers/FailFast.java new file mode 100644 index 000000000..de90f4a53 --- /dev/null +++ b/airline-core/src/main/java/com/github/rvesse/airline/parser/errors/handlers/FailFast.java @@ -0,0 +1,33 @@ +/** + * Copyright (C) 2010-16 the original author or authors. + * + * Licensed 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 com.github.rvesse.airline.parser.errors.handlers; + +import com.github.rvesse.airline.parser.ParseResult; +import com.github.rvesse.airline.parser.ParseState; +import com.github.rvesse.airline.parser.errors.ParseException; + +public class FailFast implements ParserErrorHandler { + + @Override + public void handleError(ParseException e) { + throw e; + } + + @Override + public ParseResult finished(ParseState state) { + return new ParseResult(state, null); + } +} diff --git a/airline-core/src/main/java/com/github/rvesse/airline/parser/errors/handlers/ParserErrorHandler.java b/airline-core/src/main/java/com/github/rvesse/airline/parser/errors/handlers/ParserErrorHandler.java new file mode 100644 index 000000000..5c63d7f94 --- /dev/null +++ b/airline-core/src/main/java/com/github/rvesse/airline/parser/errors/handlers/ParserErrorHandler.java @@ -0,0 +1,46 @@ +/** + * Copyright (C) 2010-16 the original author or authors. + * + * Licensed 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 com.github.rvesse.airline.parser.errors.handlers; + +import com.github.rvesse.airline.parser.ParseResult; +import com.github.rvesse.airline.parser.ParseState; +import com.github.rvesse.airline.parser.errors.ParseException; + +/** + * Interface for parser error handlers + * + * @author rvesse + * + */ +public interface ParserErrorHandler { + + /** + * Handlers an error + * + * @param e + * Error + */ + public abstract void handleError(ParseException e); + + /** + * Prepares the parser result + * + * @param state + * Parser state + * @return Parser result + */ + public ParseResult finished(ParseState state); +}