/*
Реализация спецификаций CLDC версии 1.1 (JSR-139), MIDP версии 2.1 (JSR-118)
и других спецификаций для функционирования компактных приложений на языке
Java (мидлетов) в среде программного обеспечения Малик Эмулятор.
Copyright © 2016–2017, 2019–2023 Малик Разработчик
Это свободная программа: вы можете перераспространять ее и/или изменять
ее на условиях Меньшей Стандартной общественной лицензии GNU в том виде,
в каком она была опубликована Фондом свободного программного обеспечения;
либо версии 3 лицензии, либо (по вашему выбору) любой более поздней версии.
Эта программа распространяется в надежде, что она будет полезной,
но БЕЗО ВСЯКИХ ГАРАНТИЙ; даже без неявной гарантии ТОВАРНОГО ВИДА
или ПРИГОДНОСТИ ДЛЯ ОПРЕДЕЛЕННЫХ ЦЕЛЕЙ. Подробнее см. в Меньшей Стандартной
общественной лицензии GNU.
Вы должны были получить копию Меньшей Стандартной общественной лицензии GNU
вместе с этой программой. Если это не так, см.
<https://www.gnu.org/licenses/>.
*/
package malik.emulator.application;
import java.io.*;
import java.util.*;
import malik.emulator.fileformats.*;
import malik.emulator.fileformats.text.mapped.*;
import malik.emulator.io.cloud.*;
import malik.emulator.media.graphics.*;
import malik.emulator.util.*;
public final class Run extends Requestable implements ThreadTerminationListener
{
static final int ACTION_APP_LAUNCH = 0x10;
static final int ACTION_APP_TERMINATE = 0x11;
private static final int ACTION_VIRTUAL_KEYBOARD = 0x0020;
public static final Run instance;
static {
instance = new Run();
}
public static long workingTimeMillis() {
return MalikSystem.syscall(0L, 0x000d);
}
public static String getLocalizedApplicationError() {
return "Ошибка приложения";
}
static void main() {
instance.execute();
}
private static void loadFromFile(InputAdapter adapter, String fileName) {
HandleInputStream stream;
if(!(stream = new FileInputStream("/META-INF/MANIFEST.MF")).hasOpenError())
{
try
{
try
{
adapter.loadFromInputStream(stream);
}
finally
{
stream.close();
}
}
catch(IOException e)
{
e.printRealStackTrace();
}
}
}
private static AppProxy getStartAppProxyInstance() {
String typeName;
AppProxy result = null;
if((typeName = System.getSystemProperty("malik.emulator.application.app.proxy.class")) == null)
{
System.err.println("Run: отсутствует важное системное свойство malik.emulator.application.app.proxy.class. Проверьте файл config.properties и исходный код системных библиотек.");
return null;
}
try
{
result = (AppProxy) Class.forName(typeName).newInstance();
}
catch(Exception e)
{
e.printRealStackTrace();
}
return result;
}
private AppProxy proxyActions;
private ThreadTerminationListener proxyThreads;
private KeyboardEvent eventKeyboard;
private PointerEvent eventPointer;
private final Hashtable playerListeners;
private final RunnableQueue queueThreads;
private final RequestableQueue queueActions;
private final AttributedTextDecoder appManifest;
private final InputDevice deviceKeyboardVirtual;
private final InputDevice deviceKeyboardStandard;
private final InputDevice devicePointingStandard;
private final Paint fillScreenParameters;
private final Paint drawMessageParameters;
private Run() {
int messageHeight;
SystemFont font;
AttributedTextDecoder manifest;
messageHeight = (font = SystemFont.getDefault()).getHeight();
loadFromFile(manifest = new ManifestDecoder(), "/META-INF/MANIFEST.MF");
this.eventKeyboard = new KeyboardEvent();
this.eventPointer = new PointerEvent();
this.playerListeners = new Hashtable();
this.queueThreads = new RunnableQueue();
this.queueActions = new RequestableQueue();
this.appManifest = manifest;
this.deviceKeyboardVirtual = InputDevice.get(InputDevice.ID_KEYBOARD_VIRTUAL);
this.deviceKeyboardStandard = InputDevice.get(InputDevice.ID_KEYBOARD_STANDARD);
this.devicePointingStandard = InputDevice.get(InputDevice.ID_POINTING_STANDARD);
this.fillScreenParameters = new Paint(RasterCanvas.MAX_SCREEN_WIDTH, RasterCanvas.MAX_SCREEN_HEIGHT);
this.drawMessageParameters = new TextPaint(RasterCanvas.MAX_SCREEN_WIDTH, messageHeight, font, false, false);
}
public void request(int action, int param1, int param2, int param3) {
RequestableQueue monitor;
synchronized(monitor = queueActions)
{
monitor.addTailElement(null, (long) (action & 0xffff) | (long) (param1 & 0xffff) << 0x10 | (long) (param2 & 0xffff) << 0x20 | (long) (param3 & 0xffff) << 0x30);
monitor.notify();
}
}
public void request(Runnable action, long delay) {
RunnableQueue monitor;
if(action == null) return;
if(delay > 0L)
{
Scheduler.schedule(new DelayedAction(action), delay, Scheduler.ACTION);
return;
}
synchronized(monitor = queueActions)
{
monitor.addTailElement(action);
monitor.notify();
}
}
public void terminate() {
RequestableQueue monitor;
super.terminate();
synchronized(monitor = queueActions)
{
monitor.addTailElement(null, (long) ACTION_APP_TERMINATE);
monitor.notify();
}
}
public void threadTerminated(Thread terminatedThread, Throwable exitThrowable) {
if(exitThrowable != null)
{
drawMessage(getLocalizedApplicationError());
exitThrowable.printRealStackTrace();
terminate();
}
}
public void requestVirtualKeyboardEvent(int action, int key, int charCode) {
if(action != ACTION_KEY_REPEATED && action != ACTION_KEY_PRESSED && action != ACTION_KEY_RELEASED)
{
throw new IllegalArgumentException("Run.requestVirtualKeyboardEvent: аргумент action имеет недопустимое значение.");
}
request(action | ACTION_VIRTUAL_KEYBOARD, key, charCode & 0xffff, charCode >>> 0x10);
}
public void fillScreen(int colorRGB) {
synchronized(appManifest)
{
Paint paint;
RasterCanvas canvas = RasterCanvas.screen;
(paint = fillScreenParameters).setColor(colorRGB, false);
canvas.fillRectangle(0, 0, canvas.getWidth(), canvas.getHeight(), paint);
canvas.updateScreen();
}
}
public void drawMessage(String message) {
synchronized(appManifest)
{
int w;
int h;
Paint paint;
RasterCanvas canvas = RasterCanvas.screen;
(paint = drawMessageParameters).setColor(0xc0404040, true);
paint.translateTo(0, ((865 * canvas.getHeight()) >> 10) - ((h = paint.getHeight()) >> 1));
paint.setClip(0, 0, w = canvas.getWidth(), h);
canvas.fillRectangle(0, 0, w, h, paint);
if(message != null)
{
SystemFont font = SystemFont.getDefault();
paint.setColor(0xffffff, false);
canvas.drawString(message, (w - font.stringWidth(message)) >> 1, font.getBaselinePosition(), paint);
}
canvas.updateScreen();
}
}
public void setAppProxy(AppProxy proxy) {
this.proxyActions = proxy;
}
public void setPointerEvent(PointerEvent event) {
if(event == null)
{
throw new NullPointerException("Run.setPointerEvent: аргумент event равен нулевой ссылке.");
}
eventPointer = event;
}
public void setKeyboardEvent(KeyboardEvent event) {
if(event == null)
{
throw new NullPointerException("Run.setKeyboardEvent: аргумент event равен нулевой ссылке.");
}
eventKeyboard = event;
}
public void setThreadTerminationListener(ThreadTerminationListener listener) {
proxyThreads = listener;
}
public void setPCMPlayerListener(int playerHandle, PCMPlayerListener listener) {
setPlayerListener(playerHandle, listener);
}
public void setMIDIPlayerListener(int playerHandle, MIDIPlayerListener listener) {
setPlayerListener(playerHandle, listener);
}
public String[] getAppPropertyAttributes(String key) {
return appManifest.getAttributes(key);
}
public String getAppProperty(String key) {
return appManifest.get(key);
}
public PointerEvent getPointerEvent() {
return eventPointer;
}
public KeyboardEvent getKeyboardEvent() {
return eventKeyboard;
}
void requestThreadTerminated(Thread terminatedThread, Throwable exitThrowable) {
Object monitor;
synchronized(monitor = queueActions)
{
queueThreads.addTailElement(new ThreadTerminationRecord(terminatedThread, exitThrowable));
monitor.notify();
}
}
void notifyPlayerListener(int playerHandle, int blockIndexForLoad) {
Object listener;
if((listener = playerListeners.get(new Integer(playerHandle))) instanceof PCMPlayerListener)
{
((PCMPlayerListener) listener).endOfBlock(playerHandle, blockIndexForLoad);
}
if(listener instanceof MIDIPlayerListener)
{
((MIDIPlayerListener) listener).endOfTrack(playerHandle);
}
}
ThreadTerminationListener getThreadTerminationListener() {
ThreadTerminationListener result;
return (result = proxyThreads) == null ? this : result;
}
private void execute() {
Throwable exitThrowable;
ThreadTerminationListener listener;
ThreadTerminationListenerCollection.instance.addListener(listener = new ThreadTerminationRequest());
try
{
exitThrowable = null;
try
{
ThrowableStackTrace.enable();
drawMessage("Пожалуйста, подождите…");
if(findAppProxy())
{
registerInterruptHandlers();
lifecycle();
}
}
catch(Throwable e)
{
exitThrowable = e;
}
}
finally
{
ThreadTerminationListenerCollection.instance.removeListener(listener);
}
MalikInterrupt.disable();
try
{
getThreadTerminationListener().threadTerminated(Thread.currentThread(), exitThrowable);
}
catch(Throwable e)
{
/* проигнорировать исключение */
}
System.out.close();
System.err.close();
}
private void lifecycle() {
RunnableQueue queueThreads = this.queueThreads;
RequestableQueue queueActions = this.queueActions;
for(; ; )
{
try
{
boolean empty;
long actionByUser;
Runnable actionByApplication;
waitAction();
empty = false;
actionByUser = 0L;
actionByApplication = null;
synchronized(queueActions)
{
if(!queueThreads.isEmpty())
{
actionByApplication = queueThreads.peekHeadRunnable();
((Queue) queueThreads).removeHeadElement();
}
else if(!queueActions.isEmpty())
{
actionByUser = queueActions.peekHeadLong();
actionByApplication = queueActions.peekHeadRunnable();
((Queue) queueActions).removeHeadElement();
}
else
{
empty = true;
}
}
if(empty) continue;
if(actionByApplication != null)
{
actionByApplication.run();
continue;
}
if(handleActionMustTerminated(actionByUser)) break;
}
catch(RuntimeException e)
{
e.printRealStackTrace();
}
}
}
private void waitAction() {
Object monitor;
synchronized(monitor = queueActions)
{
for(; ; )
{
try
{
monitor.wait();
break;
}
catch(InterruptedException e)
{
e.printStackTrace();
}
}
}
}
private void registerInterruptHandlers() {
InterruptLong handler = new PlayerInterruptHandler();
MalikInterrupt.register(0x10, new KeyboardInterruptHandler());
MalikInterrupt.register(0x11, new PointerInterruptHandler());
MalikInterrupt.register(0x12, new WindowShowHideInterruptHandler());
MalikInterrupt.register(0x13, new WindowResizeInterruptHandler());
MalikInterrupt.register(0x1e, handler);
MalikInterrupt.register(0x1f, handler);
Runtime.getRuntime().startInterruptHandling();
}
private void setPlayerListener(int playerHandle, Object listener) {
Object key = new Integer(playerHandle);
if(listener == null)
{
playerListeners.remove(key);
return;
}
playerListeners.put(key, listener);
}
private boolean findAppProxy() {
AppProxy proxy;
if((proxy = getStartAppProxyInstance()) == null)
{
System.err.println("Реализация AppProxy не найдена: запуск программы невозможен.");
drawMessage(getLocalizedApplicationError());
return false;
}
proxyActions = proxy;
request(ACTION_APP_LAUNCH, 0, 0, 0);
return true;
}
private boolean handleActionMustTerminated(long code) {
int action;
int param1;
int param2;
int param3;
AppProxy proxy;
InputDevice device;
PointerEvent ptrEvent;
KeyboardEvent kbdEvent;
if((proxy = proxyActions) == null) return (char) code == ACTION_APP_TERMINATE;
action = (char) code;
param1 = (char) (code >> 0x10);
param2 = (char) (code >> 0x20);
param3 = (char) (code >> 0x30);
ptrEvent = eventPointer;
kbdEvent = eventKeyboard;
ptrEvent.translateReset();
switch(action)
{
default:
break;
case ACTION_WINDOW_RESIZE:
proxy.windowSizeChanged(param2, param3);
break;
case ACTION_WINDOW_SHOW:
proxy.windowShow();
break;
case ACTION_WINDOW_HIDE:
proxy.windowHide();
break;
case ACTION_KEY_REPEATED:
case ACTION_KEY_PRESSED:
case ACTION_KEY_RELEASED:
case ACTION_KEY_REPEATED | ACTION_VIRTUAL_KEYBOARD:
case ACTION_KEY_PRESSED | ACTION_VIRTUAL_KEYBOARD:
case ACTION_KEY_RELEASED | ACTION_VIRTUAL_KEYBOARD:
device = (action & ACTION_VIRTUAL_KEYBOARD) == 0 ? deviceKeyboardStandard : deviceKeyboardVirtual;
kbdEvent.addStory(action & 0x0f, param1, param2 | param3 << 0x0010, device, InputDevice.SOURCE_KEYBOARD, 0L);
proxy.keyboardEvent(kbdEvent);
break;
case ACTION_POINTER_DRAGGED:
case ACTION_POINTER_PRESSED:
case ACTION_POINTER_RELEASED:
case ACTION_BUTTON_PRESSED:
case ACTION_BUTTON_RELEASED:
device = devicePointingStandard;
ptrEvent.addStory(action, param1, param2, param3, device, InputDevice.SOURCE_MOUSE, 0L);
proxy.pointerEvent(ptrEvent);
break;
case ACTION_APP_LAUNCH:
proxy.appLaunch();
break;
case ACTION_APP_TERMINATE:
proxy.appTerminate();
return true;
}
return false;
}
}
abstract class InputDeviceInterruptHandler extends Object implements Interrupt, InterruptLong
{
protected InputDeviceInterruptHandler() {
}
public abstract void interrupt(long argument);
}
final class KeyboardInterruptHandler extends InputDeviceInterruptHandler
{
public KeyboardInterruptHandler() {
}
public void interrupt(long argument) {
int key = (int) argument & 0xff;
int charCode = (int) (argument >> 32) & 0x00ffffff;
int action = (argument >> 63) == 0L ? Requestable.ACTION_KEY_PRESSED : Requestable.ACTION_KEY_RELEASED;
Run.instance.requestKeyboardEvent(action, key, charCode);
}
}
final class PointerInterruptHandler extends InputDeviceInterruptHandler
{
private static int hardwareButtonToPointerEventButton(int hardwareButton) {
switch(hardwareButton)
{
default:
return PointerEvent.BUTTON_MAIN;
case 1:
return PointerEvent.BUTTON_AUX_1;
case 2:
return PointerEvent.BUTTON_WHEEL;
case 4:
return PointerEvent.BUTTON_WHEEL_UP;
case 5:
return PointerEvent.BUTTON_WHEEL_DOWN;
}
}
public PointerInterruptHandler() {
}
public void interrupt(long argument) {
int e = (int) (argument >> 32) & 0x0f;
int rawX = (short) argument;
int rawY = (short) (argument >> 16);
int button = hardwareButtonToPointerEventButton(e & 0x07);
int action = e == 0x0f ? Requestable.ACTION_POINTER_DRAGGED : (e & 0x08) == 0 ? Requestable.ACTION_POINTER_PRESSED : Requestable.ACTION_POINTER_RELEASED;
Run.instance.requestPointerEvent(action, button, rawX, rawY);
}
}
final class WindowShowHideInterruptHandler extends InputDeviceInterruptHandler
{
public WindowShowHideInterruptHandler() {
}
public void interrupt(long argument) {
if(((int) (argument >> 32) & 0x01) == 0)
{
Run.instance.requestWindowShow();
return;
}
Run.instance.requestWindowHide();
}
}
final class WindowResizeInterruptHandler extends InputDeviceInterruptHandler
{
public WindowResizeInterruptHandler() {
}
public void interrupt(long argument) {
int width = (short) argument;
int height = (short) (argument >> 16);
Run.instance.requestWindowResize(width, height);
}
}
final class PlayerInterruptHandler extends InputDeviceInterruptHandler
{
public PlayerInterruptHandler() {
}
public void interrupt(long argument) {
int playerHandle = (int) argument;
int blockIndexForLoad = (int) (argument >> 32);
Run.instance.notifyPlayerListener(playerHandle, blockIndexForLoad);
}
}
final class DelayedAction extends Scheduler.Task
{
private final Runnable action;
public DelayedAction(Runnable action) {
this.action = action;
}
public void run() {
action.run();
}
}
final class ThreadTerminationRequest extends Object implements ThreadTerminationListener
{
public ThreadTerminationRequest() {
}
public void threadTerminated(Thread terminatedThread, Throwable exitThrowable) {
Run.instance.requestThreadTerminated(terminatedThread, exitThrowable);
}
}
final class ThreadTerminationRecord extends Object implements Runnable
{
private final Thread terminatedThread;
private final Throwable exitThrowable;
public ThreadTerminationRecord(Thread terminatedThread, Throwable exitThrowable) {
this.terminatedThread = terminatedThread;
this.exitThrowable = exitThrowable;
}
public void run() {
try
{
Run.instance.getThreadTerminationListener().threadTerminated(terminatedThread, exitThrowable);
}
catch(Throwable e)
{
}
}
}