1: package com.aimms.aimmssdk.examples.callback; 2: 3: import com.aimms.aimmssdk.*; 4: import java.util.ArrayList; 5: import org.slf4j.Logger; 6: import org.slf4j.LoggerFactory; 7: 8: /** 9: * This example demonstrates the custom handling of errors. It overrides the 10: * default behavior in which AimmsExceptions are thrown in all error cases in 11: * two ways: The LoggingCallback logs all minor errors, and keeps throwing the 12: * severe ones The DebuggingCallback logs as well, and asserts where errors can 13: * only be expected if the model was called erroneously. 14: */ 15: class Callback { 16: 17: static private Logger logger = LoggerFactory.getLogger(Callback.class); 18: 19: static class LoggingCallback implements ICallBack { 20: 21: public void onMessages(ArrayList<AimmsMessage> messages) { 22: // Overridden behavior: 23: // Only throw if an error is caused by the communication layer or the AIMMS model. 24: // Log the message in other cases. 25: 26: for (int m = 0; m < messages.size(); ++m) { 27: AimmsMessage msg = messages.get(m); 28: switch (msg.code) { 29: case AM_Info: 30: logger.info(msg.message); 31: break; 32: case AM_Warning: 33: logger.warn(msg.message); 34: break; 35: case AM_InvalidUse: 36: logger.error("InvalidUse: {}", msg.message); 37: break; 38: case AM_Runtime: 39: logger.error("Runtime: {}", msg.message); 40: break; 41: case AM_License: 42: throw new AimmsLicenseException(msg.message); 43: case AM_Communication: 44: throw new AimmsCommunicationException(msg.message); 45: case AM_General: // AIMMS Model errors 46: throw new AimmsException(msg.code, msg.message); 47: default: 48: throw new AimmsException(msg.code, msg.message); 49: } 50: 51: // The rationale behind this distinction is that in the cases in which exceptions are thrown now, 52: // the state of the data is not known. For example: if during the flush of the buffers an error on 53: // a specific value is detected by the AIMMS model, it is not known which part of the remaining data 54: // is already written to the model. 55: } 56: } 57: } 58: 59: static class DebuggingCallback implements ICallBack { 60: 61: 62: public void onMessages(ArrayList<AimmsMessage> messages) { 63: // Overridden behavior: 64: // 65: // assert whether an AM_InvalidUse is encountered, because that is an indication of a bug. 66: // 67: // For example: adding a string to a numerical parameter gives AM_InvalidUse. 68: 69: 70: for (int m = 0; m < messages.size(); ++m) { 71: AimmsMessage msg = messages.get(m); 72: 73: switch (msg.code) { 74: case AM_InvalidUse: 75: logger.error(msg.message); 76: assert (false); 77: break; 78: default: 79: logger.error("{}:{}", msg.code, msg.message); 80: break; 81: } 82: 83: } 84: } 85: } 86: 87: 88: 89: public static void run(ISession session) { 90: LoggingCallback cb = new LoggingCallback(); 91: //Attach the custom Callback to the session. 92: session.setCallBack(cb); 93: 94: //To illustrate the behavior of our callback, make some mistakes: 95: 96: //Try to retrieve the set Locations as a multidimensional parameter. 97: //This is an InvalidUse error: 98: // LoggingCallback will not throw 99: // DebuggingCallback will assert here. 100: IMultiDimData parameterLocations = session.openMultiDim("Locations"); 101: 102: if (parameterLocations == null) { 103: logger.error("Expected an InvalidUse error!"); 104: } 105: 106: IMultiDimData parameterSupply = session.openMultiDim("Supply"); 107: //Do not forget to check the result if the callback does not throw on each error! 108: if (parameterSupply == null) { 109: logger.error("Did not expect an error here!"); 110: } else { 111: // Since we did not add any depots, we will get a runtime error here: 112: parameterSupply.setValue(new Tuple("London"), 20.0); 113: 114: // Using "insert" will resolve this error, since it adds London to the index domain of Supply if not existing. 115: parameterSupply.insert(new Tuple("London"), 20.0); 116: 117: // Supply is a numeric parameter: it will not allow strings as value: 118: // DebuggingCallback will assert. 119: parameterSupply.insert(new Tuple("Paris"), "thirty"); 120: } 121: 122: IMultiDimData parameterDemand = session.openMultiDim("Demand"); 123: 124: if (parameterDemand == null) { 125: logger.error("Did not expect an error here!"); 126: } else { 127: ITuple tup = parameterDemand.createTuple(); 128: 129: // "London" exists in the set Locations (because we added it to Depots by inserting in Supply), 130: // but the index domain of Demand is Customers, which is still empty. 131: // The nocheck_ functions are less expensive because they do not check this in advance. 132: // Cout will not show an error after these actions: 133: tup.getElement(0).nocheck_setLabel("London"); 134: parameterDemand.setValue(tup, 90); 135: tup.close(); 136: 137: // But this performance improvement comes with a downside: 138: try { 139: // The mistake will become apparent when the data is sent to the model. 140: // Then it is undetermined wether the inserted value for "London" in Supply is indeed inserted in Aimms. 141: // If Londen is added to the domainset and this set is flushed before the Supply is flushed, 142: // the Supply of Londen will be set to its new value without an error. 143: // If Londen is not added or Supply is flushed earlier (which may be due to an internal flush), 144: // then an error will occur and even other buffered modifications to Supply may not have been inserted in Aimms. 145: session.updateData(); 146: 147: } catch (AimmsException e) { 148: logger.error(e.getMessage()); 149: } 150: 151: parameterSupply.close(); 152: parameterDemand.close(); 153: } 154: } 155: }