Donate to e Foundation | Murena handsets with /e/OS | Own a part of Murena! Learn more

Commit 3dc4af47 authored by Winson's avatar Winson
Browse files

Add ParseResult infrastructure

ParseInput is passed into all parsing methods and simply acts as
a shared container which can hold a generic success or error value.
When it is transformed into a result value, it must be returned to
its parent immediately, who can decide what to do with it.

ParseResult is the type returned when a ParseInput is set to success
or error. It casts itself to a strong type representing the returned
value, and is used by specifying ParseResult<ResultType> instead of
just ResultType for the method return type.

ParseTypeImpl is just the implementation of the two above which handles
moving between them. It also adds some debug functionality in case
the error catching code is incorrect or insufficient and it would be
preferably to always have a stack trace.

It is important to use this infrastructure in a thread-local manner,
such that you don't re-use an input already in use and always bubble
up on any success or error.

A constrained example:

class Parser {

    val shared = ParseTypeImpl<>()

    fun parse(file: File): Pair<Output, Output> {
        // Reset the shared object
        val input = shared.reset()

        // Pass it to be used by the parsing method
        var result = parseOutput(input, file.read())

        // Verify the result
        if (result.isError()) {
            // Bubble up error if necessary
            throw Exception(result.errorMessage, result.exception)
        }

        // Save the result (as it will be lost when the input is reused)
        val one = result.result

        // Parse something else
        result = parseOutput(input, file.read())

        // Same verification
        if (result.isError()) {
            // Bubble up error if necessary
            throw Exception(result.errorMessage, result.exception)
        }

        val two = result.result
        return one to two
    }

    fun parseOutput(input: Input, read: Object): ParseResult<Output> {
        return if (read == null) {
            input.error("Something went wrong")
        } else {
            input.success(read)
        }
    }
}

Bug: 135203078

Change-Id: I532d0047e67f80fd925b481dac8823ab45ad0194
parent 33eacc6c
Loading
Loading
Loading
Loading
+66 −0
Original line number Diff line number Diff line
/*
 * Copyright (C) 2020 The Android Open Source Project
 *
 * 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 android.content.pm.parsing.result;

import android.annotation.Hide;
import android.annotation.NonNull;
import android.annotation.Nullable;

/**
 * Used as a method parameter which is then transformed into a {@link ParseResult}. This is
 * generalized as it doesn't matter what type this input is for. It's simply to hide the
 * methods of {@link ParseResult}.
 *
 * @hide
 */
public interface ParseInput {

    <ResultType> ParseResult<ResultType> success(ResultType result);

    /** @see #error(int, String, Exception) */
    <ResultType> ParseResult<ResultType> error(int parseError);

    /** @see #error(int, String, Exception) */
    <ResultType> ParseResult<ResultType> error(@NonNull String parseError);

    /** @see #error(int, String, Exception) */
    <ResultType> ParseResult<ResultType> error(int parseError, @Nullable String errorMessage);

    /**
     * Marks this as an error result. When this method is called, the return value <b>must</b>
     * be returned to the exit of the parent method that took in this {@link ParseInput} as a
     * parameter.
     *
     * The calling site of that method is then expected to check the result for error, and
     * continue to bubble up if it is an error.
     *
     * Or, if the code explicitly handles an error,
     * {@link ParseResult#ignoreError()} should be called.
     *
     * If the result {@link ParseResult#isSuccess()}, then it can be used as-is, as
     * overlapping/consecutive successes are allowed.
     */
    <ResultType> ParseResult<ResultType> error(int parseError, @Nullable String errorMessage,
            @Nullable Exception exception);

    /**
     * Moves the error in {@param result} to this input's type. In practice this does nothing
     * but cast the type of the {@link ParseResult} for type safety, since the parameter
     * and the receiver should be the same object.
     */
    <ResultType> ParseResult<ResultType> error(ParseResult result);
}
+76 −0
Original line number Diff line number Diff line
/*
 * Copyright (C) 2020 The Android Open Source Project
 *
 * 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 android.content.pm.parsing.result;

import android.annotation.Nullable;
import android.content.pm.PackageParser;

/**
 * The output side of {@link ParseInput}, which must result from a method call on
 * {@link ParseInput}.
 *
 * When using this class, keep in mind that all {@link ParseInput}s and {@link ParseResult}s
 * are the exact same object, scoped to a per {@link PackageParser} instance, per thread basis,
 * thrown around and casted everywhere for type safety.
 *
 * @hide
 */
public interface ParseResult<ResultType> {

    /**
     * Un-marks this result as an error, also allowing it to be re-used as {@link ParseInput}.
     *
     * This should only be used in cases where it's absolutely certain that error handling is
     * irrelevant. Such as for backwards compatibility where it previously didn't fail and that
     * behavior has to be maintained.
     *
     * Mostly an alias for readability.
     */
    void ignoreError();

    /**
     * Returns true if the result is not an error and thus contains a valid object.
     *
     * For backwards-compat reasons, it's possible to have a successful result with a null
     * result object, depending on the behavior of the parsing method.
     *
     * It is expected that every method calls this to check for an error state to bubble up
     * the error to its parent method after every parse method call.
     *
     * It is not always necessary to check this, as it is valid to return any ParseResult from
     * a method so long as the type matches <b>without casting it</b>.
     *
     * The infrastructure is set up such that as long as a result is the proper type and
     * the right side of success vs. error, it can be bubble up through all its parent methods.
     */
    boolean isSuccess();

    /**
     * Opposite of {@link #isSuccess()} for readability.
     */
    boolean isError();

    ResultType getResult();

    int getErrorCode();

    @Nullable
    String getErrorMessage();

    @Nullable
    Exception getException();
}
+145 −0
Original line number Diff line number Diff line
/*
 * Copyright (C) 2020 The Android Open Source Project
 *
 * 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 android.content.pm.parsing.result;

import android.annotation.NonNull;
import android.annotation.Nullable;
import android.content.pm.PackageManager;
import android.content.pm.parsing.ParsingUtils;
import android.util.Log;

import java.util.Arrays;

/** @hide */
public class ParseTypeImpl implements ParseInput, ParseResult<Object> {

    private static final String TAG = ParsingUtils.TAG;

    private static final boolean DEBUG_FILL_STACK_TRACE = false;

    private static final boolean DEBUG_LOG_ON_ERROR = false;

    private Object result;

    private int errorCode = PackageManager.INSTALL_SUCCEEDED;

    @Nullable
    private String errorMessage;

    @Nullable
    private Exception exception;

    public ParseInput reset() {
        this.result = null;
        this.errorCode = PackageManager.INSTALL_SUCCEEDED;
        this.errorMessage = null;
        this.exception = null;
        return this;
    }

    @Override
    public void ignoreError() {
        reset();
    }

    @Override
    public <ResultType> ParseResult<ResultType> success(ResultType result) {
        if (errorCode != PackageManager.INSTALL_SUCCEEDED || errorMessage != null) {
            throw new IllegalStateException("Cannot set to success after set to error, was "
                    + errorMessage, exception);
        }
        this.result = result;
        //noinspection unchecked
        return (ParseResult<ResultType>) this;
    }

    @Override
    public <ResultType> ParseResult<ResultType> error(int parseError) {
        return error(parseError, null);
    }

    @Override
    public <ResultType> ParseResult<ResultType> error(@NonNull String parseError) {
        return error(PackageManager.INSTALL_PARSE_FAILED_MANIFEST_MALFORMED, parseError);
    }

    @Override
    public <ResultType> ParseResult<ResultType> error(int errorCode,
            @Nullable String errorMessage) {
        return error(errorCode, errorMessage, null);
    }

    @Override
    public <ResultType> ParseResult<ResultType> error(ParseResult intentResult) {
        return error(intentResult.getErrorCode(), intentResult.getErrorMessage());
    }

    @Override
    public <ResultType> ParseResult<ResultType> error(int errorCode, @Nullable String errorMessage,
            Exception exception) {
        this.errorCode = errorCode;
        this.errorMessage = errorMessage;
        this.exception = exception;

        if (DEBUG_FILL_STACK_TRACE) {
            if (exception == null) {
                this.exception = new Exception();
            }
        }

        if (DEBUG_LOG_ON_ERROR) {
            Exception exceptionToLog = this.exception != null ? this.exception : new Exception();
            Log.w(TAG, "ParseInput set to error " + errorCode + ", " + errorMessage,
                    exceptionToLog);
        }

        //noinspection unchecked
        return (ParseResult<ResultType>) this;
    }

    @Override
    public Object getResult() {
        return this.result;
    }

    @Override
    public boolean isSuccess() {
        return errorCode == PackageManager.INSTALL_SUCCEEDED;
    }

    @Override
    public boolean isError() {
        return !isSuccess();
    }

    @Override
    public int getErrorCode() {
        return errorCode;
    }

    @Nullable
    @Override
    public String getErrorMessage() {
        return errorMessage;
    }

    @Nullable
    @Override
    public Exception getException() {
        return exception;
    }
}