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.kernel.AbstractNativeRunner;
029import org.ocamljava.runtime.kernel.Fail;
030import org.ocamljava.runtime.kernel.Misc;
031import org.ocamljava.runtime.kernel.OCamlJavaThread;
032import org.ocamljava.runtime.values.Value;
033
034/**
035 * The {@code OCamlException} class is the parent class of all wrapped
036 * OCaml exceptions, also acting as a fallback cases for when the wrapped
037 * exception cannot be precisely mapped.
038 *
039 * @author <a href="mailto:xclerc@ocamljava.org">Xavier Clerc</a>
040 * @version 2.0
041 * @since 2.0
042 */
043public class OCamlException extends RuntimeException {
044
045    /** Serialization UID. */
046    static final long serialVersionUID = 304215162058080925L;
047
048    /** Identifier for {@code OCamlException}. */
049    public static final Object SLOT = new Object();
050
051    static {
052        // registration is done here rather than in exception classes
053        // themselves because the static blocks are executed iff a class
054        // is referenced from a program
055        try {
056            OCamlException.register("Not_found", OCamlNotFoundException.class);
057            OCamlException.register("Out_of_memory", OCamlOutOfMemoryException.class);
058            OCamlException.register("Stack_overflow", OCamlStackOverflowException.class);
059            OCamlException.register("End_of_file", OCamlEndOfFileException.class);
060            OCamlException.register("Division_by_zero", OCamlDivisionByZeroException.class);
061            OCamlException.register("Sys_blocked_io", OCamlSysBlockedIOException.class);
062            OCamlException.register("Failure", OCamlFailureException.class);
063            OCamlException.register("Invalid_argument", OCamlInvalidArgumentException.class);
064            OCamlException.register("Sys_error", OCamlSysErrorException.class);
065            OCamlException.register("Match_failure", OCamlMatchFailureException.class);
066            OCamlException.register("Assert_failure", OCamlAssertFailureException.class);
067            OCamlException.register("Undefined_recursive_module", OCamlUndefinedRecursiveModuleException.class);
068        } catch (final Throwable t) {
069            // nothing to do
070        } // end try/catch
071    } // end static block
072
073    /**
074     * Constructs a new instance from an exception.
075     * @param fe exception to build instance from - should not be {@code null}
076     */
077    public OCamlException(final Fail.Exception fe) {
078        super(fe);
079        setStackTrace(fe.getStackTrace());
080    } // end constructor(Fail.Exception)
081
082    /**
083     * Constructs a new instance from a value. <br/>
084     * Equivalent to {@code OCamlException(new Fail.Exception(v))}
085     * @param v value to build instance from - should not be {@code null}
086     */
087    public OCamlException(final Value v) {
088        super(new Fail.Exception(v));
089    } // end constructor(Value)
090
091    /**
092     * Returns the underlying exception.
093     * @return the underlying exception
094     */
095    public final Fail.Exception getFailException() {
096        return (Fail.Exception) getCause();
097    } // end method 'getFailException()'
098
099    /**
100     * Returns the name (that acts as an identifier) of the underlying exception.
101     * @return the name of the underlying exception
102     */
103    public final String getOCamlName() {
104        final Fail.Exception e = (Fail.Exception) getCause();
105        final Value v = e.asValue();
106        return v.get0().get0().asString();
107    } // end method 'getOCamlName()'
108
109    /**
110     * Returns the string representation of the underlying exception.
111     * @return the string representation of the underlying exception
112     */
113    public final String getOCamlStringRepresentation() {
114        final Fail.Exception e = (Fail.Exception) getCause();
115        final Value v = e.asValue();
116        return Misc.convertException(v, null);
117    } // end method 'getOCamlStringRepresentation()'
118
119    /**
120     * {@inheritDoc}
121     */
122    @Override
123    public final String toString() {
124        return getOCamlStringRepresentation();
125    } // end method 'toString()'
126
127    /**
128     * Wraps a {@code Fail.Exception} exception into a {@code OCamlException} one,
129     * using the map of registered exceptions.
130     * @param fe exception to wrap - should not be {@code null}
131     * @return a new {@code OCamlException} instance wrapping the passed exception
132     */
133    public static OCamlException wrap(final Fail.Exception fe) {
134        assert fe != null : "null fe";
135        final Value v = fe.asValue();
136        final String id = v.get0().get0().asString();
137        final Constructor<?> cstr = getExceptionMap().get(id);
138        if (cstr != null) {
139            try {
140                return (OCamlException) cstr.newInstance(fe);
141            } catch (final InstantiationException | IllegalAccessException | InvocationTargetException e) {
142                return new OCamlException(fe);
143            } // end try/catch
144        } else {
145            return new OCamlException(fe);
146        } // end if/else
147    } // end method 'wrap(Fail.Exception)'
148
149    /**
150     * Registers an exception in the context. <br/>
151     * The class implementing the exception should provide a constructor
152     * with signature {@code (Fail.Exception)}.
153     * @param id exception identifier - should not be {@code null}
154     * @param cl class of exception to register - should not be {@code null}
155     */
156    public static void register(final String id, final Class<?> cl) {
157        assert id != null : "null id";
158        assert cl != null : "null cl";
159        try {
160            final Map<String, Constructor<?>> map = OCamlException.getExceptionMap();
161            map.put(id, cl.getConstructor(Fail.Exception.class));
162        } catch (final NoSuchMethodException nsme) {
163            // nothing to do
164        } // end try/catch
165    } // end method 'register(String, Class<?>)'
166
167    /**
168     * Returns the tag for the predefined exception whose identifier is given.
169     * @param id predefined exception identifier - should not be {@code null}
170     * @return the tag for the predefined exception whose identifier is given,
171     *         {@code null} if such an exception does not exit
172     */
173    static Value getTag(final String id) {
174        assert id != null : "null id";
175        final AbstractNativeRunner runner = (AbstractNativeRunner)
176            OCamlJavaThread.getCodeRunner();
177        switch (id) {
178        case "Out_of_memory": return runner.exnOutOfMemory;
179        case "Sys_error": return runner.exnSysError;
180        case "Failure": return runner.exnFailure;
181        case "Invalid_argument": return runner.exnInvalidArgument;
182        case "End_of_file": return runner.exnEndOfFile;
183        case "Division_by_zero": return runner.exnDivisionByZero;
184        case "Not_found": return runner.exnNotFound;
185        case "Match_failure": return runner.exnMatchFailure;
186        case "Stack_overflow": return runner.exnStackOverflow;
187        case "Sys_blocked_io": return runner.exnSysBlockedIO;
188        case "Assert_failure": return runner.exnAssertFailure;
189        case "Undefined_recursive_module": return runner.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'