001/*
002 * This file is part of OCaml-Java runtime.
003 * Copyright (C) 2007-2013 Xavier Clerc.
004 *
005 * OCaml-Java runtime is free software; you can redistribute it and/or modify
006 * it under the terms of the GNU Lesser General Public License as published by
007 * the Free Software Foundation; either version 3 of the License, or
008 * (at your option) any later version.
009 *
010 * OCaml-Java runtime is distributed in the hope that it will be useful,
011 * but WITHOUT ANY WARRANTY; without even the implied warranty of
012 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
013 * GNU Lesser General Public License for more details.
014 *
015 * You should have received a copy of the GNU Lesser General Public License
016 * along with this program.  If not, see <http://www.gnu.org/licenses/>.
017 */
018
019package org.ocamljava.runtime.wrappers;
020
021import java.lang.reflect.Constructor;
022import java.lang.reflect.InvocationTargetException;
023import java.util.HashMap;
024import java.util.Map;
025
026import org.ocamljava.runtime.context.CodeState;
027import org.ocamljava.runtime.context.CurrentContext;
028import org.ocamljava.runtime.context.PredefinedExceptions;
029import org.ocamljava.runtime.kernel.AbstractNativeRunner;
030import org.ocamljava.runtime.kernel.FailException; import org.ocamljava.runtime.kernel.Fail;
031import org.ocamljava.runtime.kernel.Misc;
032import org.ocamljava.runtime.kernel.OCamlJavaThread;
033import org.ocamljava.runtime.values.Value;
034
035/**
036 * The {@code OCamlException} class is the parent class of all wrapped
037 * OCaml exceptions, also acting as a fallback cases for when the wrapped
038 * exception cannot be precisely mapped.
039 *
040 * @author <a href="mailto:xclerc@ocamljava.org">Xavier Clerc</a>
041 * @version 2.0
042 * @since 2.0
043 */
044public class OCamlException extends RuntimeException {
045
046    /** Serialization UID. */
047    static final long serialVersionUID = 304215162058080925L;
048
049    /** Identifier for {@code OCamlException}. */
050    public static final Object SLOT = new Object();
051
052    static {
053        // registration is done here rather than in exception classes
054        // themselves because the static blocks are executed iff a class
055        // is referenced from a program
056        try {
057            OCamlException.register("Not_found", OCamlNotFoundException.class);
058            OCamlException.register("Out_of_memory", OCamlOutOfMemoryException.class);
059            OCamlException.register("Stack_overflow", OCamlStackOverflowException.class);
060            OCamlException.register("End_of_file", OCamlEndOfFileException.class);
061            OCamlException.register("Division_by_zero", OCamlDivisionByZeroException.class);
062            OCamlException.register("Sys_blocked_io", OCamlSysBlockedIOException.class);
063            OCamlException.register("Failure", OCamlFailureException.class);
064            OCamlException.register("Invalid_argument", OCamlInvalidArgumentException.class);
065            OCamlException.register("Sys_error", OCamlSysErrorException.class);
066            OCamlException.register("Match_failure", OCamlMatchFailureException.class);
067            OCamlException.register("Assert_failure", OCamlAssertFailureException.class);
068            OCamlException.register("Undefined_recursive_module", OCamlUndefinedRecursiveModuleException.class);
069        } catch (final Throwable t) {
070            // nothing to do
071        } // end try/catch
072    } // end static block
073
074    /**
075     * Constructs a new instance from an exception.
076     * @param fe exception to build instance from - should not be {@code null}
077     */
078    public OCamlException(final FailException fe) {
079        super(fe);
080        setStackTrace(fe.getStackTrace());
081    } // end constructor(FailException)
082
083    /**
084     * Constructs a new instance from a value. <br/>
085     * Equivalent to {@code OCamlException(new FailException(v))}
086     * @param v value to build instance from - should not be {@code null}
087     */
088    public OCamlException(final Value v) {
089        super(new FailException(v));
090    } // end constructor(Value)
091
092    /**
093     * Returns the underlying exception.
094     * @return the underlying exception
095     */
096    public final FailException getFailException() {
097        return (FailException) getCause();
098    } // end method 'getFailException()'
099
100    /**
101     * Returns the name (that acts as an identifier) of the underlying exception.
102     * @return the name of the underlying exception
103     */
104    public final String getOCamlName() {
105        final FailException e = (FailException) getCause();
106        final Value v = e.getValue();
107        return v.get0().get0().asString();
108    } // end method 'getOCamlName()'
109
110    /**
111     * Returns the string representation of the underlying exception.
112     * @return the string representation of the underlying exception
113     */
114    public final String getOCamlStringRepresentation() {
115        final FailException e = (FailException) getCause();
116        final Value v = e.getValue();
117        return Misc.convertException(v, null);
118    } // end method 'getOCamlStringRepresentation()'
119
120    /**
121     * {@inheritDoc}
122     */
123    @Override
124    public final String toString() {
125        return getOCamlStringRepresentation();
126    } // end method 'toString()'
127
128    /**
129     * Wraps a {@code FailException} exception into a {@code OCamlException} one,
130     * using the map of registered exceptions.
131     * @param fe exception to wrap - should not be {@code null}
132     * @return a new {@code OCamlException} instance wrapping the passed exception
133     */
134    public static OCamlException wrap(final FailException fe) {
135        assert fe != null : "null fe";
136        final Value v = fe.getValue();
137        final String id = v.get0().get0().asString();
138        final Constructor<?> cstr = getExceptionMap().get(id);
139        if (cstr != null) {
140            try {
141                return (OCamlException) cstr.newInstance(fe);
142            } catch (final InstantiationException | IllegalAccessException | InvocationTargetException e) {
143                return new OCamlException(fe);
144            } // end try/catch
145        } else {
146            return new OCamlException(fe);
147        } // end if/else
148    } // end method 'wrap(FailException)'
149
150    /**
151     * Registers an exception in the context. <br/>
152     * The class implementing the exception should provide a constructor
153     * with signature {@code (FailException)}.
154     * @param id exception identifier - should not be {@code null}
155     * @param cl class of exception to register - should not be {@code null}
156     */
157    public static void register(final String id, final Class<?> cl) {
158        assert id != null : "null id";
159        assert cl != null : "null cl";
160        try {
161            final Map<String, Constructor<?>> map = OCamlException.getExceptionMap();
162            map.put(id, cl.getConstructor(FailException.class));
163        } catch (final NoSuchMethodException nsme) {
164            // nothing to do
165        } // end try/catch
166    } // end method 'register(String, Class<?>)'
167
168    /**
169     * Returns the tag for the predefined exception whose identifier is given.
170     * @param id predefined exception identifier - should not be {@code null}
171     * @return the tag for the predefined exception whose identifier is given,
172     *         {@code null} if such an exception does not exit
173     */
174    static Value getTag(final String id) { // XXX duplicated somewhere, should be in PredefinedExceptions
175        assert id != null : "null id";
176        final PredefinedExceptions exceptions = CurrentContext.getPredefinedExceptions();
177        switch (id) {
178        case "Out_of_memory": return exceptions.exnOutOfMemory;
179        case "Sys_error": return exceptions.exnSysError;
180        case "Failure": return exceptions.exnFailure;
181        case "Invalid_argument": return exceptions.exnInvalidArgument;
182        case "End_of_file": return exceptions.exnEndOfFile;
183        case "Division_by_zero": return exceptions.exnDivisionByZero;
184        case "Not_found": return exceptions.exnNotFound;
185        case "Match_failure": return exceptions.exnMatchFailure;
186        case "Stack_overflow": return exceptions.exnStackOverflow;
187        case "Sys_blocked_io": return exceptions.exnSysBlockedIO;
188        case "Assert_failure": return exceptions.exnAssertFailure;
189        case "Undefined_recursive_module": return exceptions.exnUndefinedRecursiveModule;
190        default: return null;
191        } // end switch
192    } // end method 'getTag(String)'
193
194    /**
195     * Returns the map of registered exceptions, as stored in the context.
196     * @return the map of registered exceptions
197     */
198    @SuppressWarnings("unchecked")
199    private static synchronized Map<String, Constructor<?>> getExceptionMap() {
200        final CodeState cs = CurrentContext.getCodeState();
201        final Map<String, Constructor<?>> res =
202            (Map<String, Constructor<?>>) cs.getSlot(OCamlException.SLOT);
203        if (res == null) {
204            final Map<String, Constructor<?>> slot = new HashMap<>();
205            cs.registerSlot(OCamlException.SLOT, slot);
206            return slot;
207        } else {
208            return res;
209        } // end if/else
210    } // end method 'getExceptionMap()'
211
212} // end class 'OCamlException'