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