package com.laytonsmith.core.exceptions.CRE;
import com.laytonsmith.PureUtilities.ClassLoading.ClassDiscovery;
import com.laytonsmith.PureUtilities.Common.ReflectionUtils;
import com.laytonsmith.annotations.seealso;
import com.laytonsmith.annotations.typeof;
import com.laytonsmith.core.Documentation;
import com.laytonsmith.core.Static;
import com.laytonsmith.core.constructs.CArray;
import com.laytonsmith.core.constructs.CClassType;
import com.laytonsmith.core.constructs.CNull;
import com.laytonsmith.core.constructs.Construct;
import com.laytonsmith.core.constructs.NativeTypeList;
import com.laytonsmith.core.constructs.Target;
import com.laytonsmith.core.exceptions.ConfigRuntimeException;
import com.laytonsmith.core.exceptions.StackTraceManager;
import com.laytonsmith.core.natives.interfaces.ArrayAccess;
import com.laytonsmith.core.natives.interfaces.Mixed;
import java.io.File;
import java.net.URL;
import java.util.ArrayList;
import java.util.List;
import java.util.Set;
/**
* Exceptions should extend this class, which provides default implementations of various utility methods.
*/
public abstract class AbstractCREException extends ConfigRuntimeException implements Documentation, Mixed, ArrayAccess {
private final static Class[] EMPTY_CLASS = new Class[0];
private List<StackTraceElement> stackTrace = null;
public AbstractCREException(String msg, Target t){
super(msg, t);
}
public AbstractCREException(String msg, Target t, Throwable cause) {
super(msg, t, cause);
}
@Override
public Class<? extends Documentation>[] seeAlso() {
seealso see = this.getClass().getAnnotation(seealso.class);
if(see == null){
return EMPTY_CLASS;
} else {
return see.value();
}
}
@Override
public URL getSourceJar() {
return ClassDiscovery.GetClassContainer(this.getClass());
}
@Override
public String getName() {
typeof to = this.getClass().getAnnotation(typeof.class);
if(to == null){
throw new Error("ConfigRuntimeException subtypes must annotate themselves with @typeof, if they are instantiateable.");
} else {
return to.value();
}
}
/**
* Alias for {@link #getName() }
* @return
*/
public String getExceptionType(){
return getName();
}
/**
* Returns the name of the exception. If the exception is an instanceof AbstractCREException,
* this is equivalent to calling {@link #getName() }. Otherwise, the java class name is returned.
* @param ex
* @return
*/
public static String getExceptionName(ConfigRuntimeException ex){
if(ex instanceof AbstractCREException){
return ((AbstractCREException)ex).getName();
} else {
return ex.getClass().getName();
}
}
private CArray exceptionObject = null;
/**
* Returns a standardized CArray given this exception.
* @return
*/
public CArray getExceptionObject(){
CArray ret = new CArray(Target.UNKNOWN);
ret.set("classType", new CClassType(this.getName(), Target.UNKNOWN), Target.UNKNOWN);
ret.set("message", this.getMessage());
CArray stackTrace = new CArray(Target.UNKNOWN);
ret.set("stackTrace", stackTrace, Target.UNKNOWN);
for(StackTraceElement e : this.getCREStackTrace()){
CArray element = e.getObjectFor();
stackTrace.push(element, Target.UNKNOWN);
}
ret.set("causedBy", getCausedBy(this.getCause()), Target.UNKNOWN);
return ret;
}
@SuppressWarnings({"ThrowableInstanceNotThrown", "ThrowableInstanceNeverThrown"})
public static AbstractCREException getFromCArray(CArray exception, Target t) throws ClassNotFoundException {
String classType = exception.get("classType", t).val();
Class<Mixed> clzz = NativeTypeList.getNativeClass(classType);
Throwable cause = null;
if(exception.get("causedBy", t) instanceof CArray){
// It has a cause
cause = new CRECausedByWrapper((CArray)exception.get("causedBy", t));
}
String message = exception.get("message", t).val();
List<StackTraceElement> st = new ArrayList<>();
for(Construct consStElement : Static.getArray(exception.get("stackTrace", t), t).asList()){
CArray stElement = Static.getArray(consStElement, t);
int line = Static.getInt32(stElement.get("line", t), t);
File f = new File(stElement.get("file", t).val());
int col = 0; //
st.add(new StackTraceElement(stElement.get("id", t).val(), new Target(line, f, col)));
}
// Now we have parsed everything into POJOs
Class[] types = new Class[]{String.class, Target.class, Throwable.class};
Object[] args = new Object[]{message, t, cause};
AbstractCREException ex = (AbstractCREException) ReflectionUtils.newInstance(clzz, types, args);
ex.stackTrace = st;
return ex;
}
private static Construct getCausedBy(Throwable causedBy){
if(causedBy == null || !(causedBy instanceof CRECausedByWrapper)){
return CNull.NULL;
}
CRECausedByWrapper cre = (CRECausedByWrapper) causedBy;
CArray ret = cre.getException();
return ret;
}
/**
* Casts the CRE to an AbstractCREException, or throws an error if it is not convertable.
* @param ex
* @return
*/
public static AbstractCREException getAbstractCREException(ConfigRuntimeException ex){
if(ex instanceof AbstractCREException){
return (AbstractCREException)ex;
}
throw new Error("Unexpected CRE exception that isn't convertable to AbstractCREException");
}
@Override
public String val() {
return getName() + ":" + getMessage();
}
/**
* These methods are required because we don't actually want to extend CArray, we want to be our own
* class of Object (at least for now). These basically just call through to our underlying CArray though.
* @param index
* @param t
* @return
* @throws ConfigRuntimeException
*/
@Override
public Construct get(String index, Target t) throws ConfigRuntimeException {
return exceptionObject.get(index, t);
}
@Override
public Construct get(int index, Target t) throws ConfigRuntimeException {
return exceptionObject.get(index, t);
}
@Override
public Construct get(Construct index, Target t) throws ConfigRuntimeException {
return exceptionObject.get(index, t);
}
@Override
public Set<Construct> keySet() {
return exceptionObject.keySet();
}
@Override
public long size() {
return exceptionObject.size();
}
@Override
public boolean isAssociative() {
return exceptionObject.isAssociative();
}
@Override
public boolean canBeAssociative() {
return exceptionObject.canBeAssociative();
}
@Override
public Construct slice(int begin, int end, Target t) {
return exceptionObject.slice(begin, end, t);
}
@Override
public AbstractCREException clone() throws CloneNotSupportedException {
AbstractCREException obj = (AbstractCREException)super.clone();
return obj;
}
public void freezeStackTraceElements(StackTraceManager manager){
if(this.stackTrace == null){
this.stackTrace = manager.getCurrentStackTrace();
}
}
public List<StackTraceElement> getCREStackTrace(){
if(this.stackTrace == null){
return new ArrayList<>();
}
return new ArrayList<>(this.stackTrace);
}
}