/*
Реализация спецификаций CLDC версии 1.1 (JSR-139), MIDP версии 2.1 (JSR-118)
и других спецификаций для функционирования компактных приложений на языке
Java (мидлетов) в среде программного обеспечения Малик Эмулятор.
Copyright © 2016, 2019 Малик Разработчик
Это свободная программа: вы можете перераспространять ее и/или изменять
ее на условиях Меньшей Стандартной общественной лицензии GNU в том виде,
в каком она была опубликована Фондом свободного программного обеспечения;
либо версии 3 лицензии, либо (по вашему выбору) любой более поздней версии.
Эта программа распространяется в надежде, что она будет полезной,
но БЕЗО ВСЯКИХ ГАРАНТИЙ; даже без неявной гарантии ТОВАРНОГО ВИДА
или ПРИГОДНОСТИ ДЛЯ ОПРЕДЕЛЕННЫХ ЦЕЛЕЙ. Подробнее см. в Меньшей Стандартной
общественной лицензии GNU.
Вы должны были получить копию Меньшей Стандартной общественной лицензии GNU
вместе с этой программой. Если это не так, см.
<https://www.gnu.org/licenses/>.
*/
package malik.emulator.application;
import java.util.*;
import malik.emulator.util.*;
public final class Run extends Object
implements ThreadTerminationListener
{
/* Следующие методы будут вызываться только из главного потока (т.е. сериализовано):
* void java.lang.Runnable.run()
* void java.lang.ThreadTerminationListener.onThreadTermination(java.lang.Thread,
* java.lang.Throwable)
* void malik.emulator.application.AppProxy.appLaunch()
* void malik.emulator.application.AppProxy.appTerminate()
* void malik.emulator.application.AppProxy.keyPressed(int, int)
* void malik.emulator.application.AppProxy.keyRepeated(int, int)
* void malik.emulator.application.AppProxy.keyReleased(int)
* void malik.emulator.application.AppProxy.pointerPressed(int, int, int)
* void malik.emulator.application.AppProxy.pointerDragged(int, int)
* void malik.emulator.application.AppProxy.pointerReleased(int, int, int)
* void malik.emulator.application.AppProxy.windowResize(int, int)
* void malik.emulator.application.AppProxy.windowShow()
* void malik.emulator.application.AppProxy.windowHide()
*/
static final int EVENT_APP_LAUNCH = 0;
static final int EVENT_APP_TERMINATE = 1;
static final int EVENT_KEY_PRESSED = 4;
static final int EVENT_KEY_RELEASED = 6;
static final int EVENT_POINTER_PRESSED = 8;
static final int EVENT_POINTER_DRAGGED = 9;
static final int EVENT_POINTER_RELEASED = 10;
static final int EVENT_WINDOW_RESIZE = 12;
static final int EVENT_WINDOW_SHOW = 13;
static final int EVENT_WINDOW_HIDE = 14;
private static final Run INSTANCE;
static
{
INSTANCE = new Run();
}
public static Run getInstance()
{
return INSTANCE;
}
static void main()
{
ThreadTerminationListenerCollection.instance.addListener(new ThreadTerminationHandler(INSTANCE));
INSTANCE.execute();
}
private static AppProxy getStartAppProxyInstance()
throws AppProxyNotFoundException
{
AppProxy result;
String className = System.getSystemProperty("malik.emulator.application.run.proxy.class");
try
{
result = (AppProxy) Class.forName(className).newInstance();
}
catch(NullPointerException e)
{
throw new AppProxyNotFoundException(
"Отсутствует важное системное свойство: " +
"malik.emulator.application.run.proxy.class. " +
"Проверьте файл config.properties и исходный код системных библиотек.");
}
catch(ClassNotFoundException e)
{
throw new AppProxyNotFoundException(
"Не удалось найти класс для обработки событий: " + className + ".");
}
catch(InstantiationException e)
{
throw new AppProxyNotFoundException(
"Не удалось создать инстанцию обработчика событий " + className + ".");
}
catch(IllegalAccessException e)
{
throw new AppProxyNotFoundException(
"Не удалось получить доступ к обработчику событий " + className + ".");
}
catch(ClassCastException e)
{
throw new AppProxyNotFoundException(
"Неподходящий класс для обработки событий: " + className + ".");
}
return result;
}
private boolean terminated;
private boolean[] keyPressed;
private String errorMessage;
private Hashtable playerHandlers;
private QueueOfObjects threadTerminations;
private QueueOfMalikAppEvents mainEvents;
private StretchDrawDescriptor stretchDescriptor;
private StringDrawDescriptor stringDescriptor;
private ThreadTerminationListener threadProxy;
private AppProxy eventsProxy;
private Run()
{
this.terminated = false;
this.keyPressed = new boolean[0x0100];
this.errorMessage = "Ошибка приложения";
this.playerHandlers = new Hashtable();
this.threadTerminations = new QueueOfObjects();
this.mainEvents = new QueueOfMalikAppEvents();
this.stretchDescriptor = new StretchDrawDescriptor();
this.stringDescriptor = new StringDrawDescriptor();
}
public void threadTerminated(Thread terminatedThread, Throwable exitThrowable)
{
if(exitThrowable == null)
{
return;
}
showMessage(errorMessage);
exitThrowable.printRealStackTrace();
terminate();
}
public void serviceImmediately(Object argument)
{
QueueOfMalikAppEvents events = mainEvents;
Object monitor = keyPressed;
for(Runnable event; ; )
{
synchronized(monitor)
{
event = events.findImmediatelyRunnable(argument);
}
if(event == null) break;
try
{
event.run();
}
catch(RuntimeException e)
{
e.printRealStackTrace();
}
}
}
public void setPlayerPCMLoadBlockHandler(int playerPCMHandle,
PlayerPCMLoadBlockHandler handler)
{
Object key = new Integer(playerPCMHandle);
if(handler == null)
{
playerHandlers.remove(key);
return;
}
playerHandlers.put(key, handler);
}
public void setPlayerMIDIEndTrackHandler(int playerMIDIHandle,
PlayerMIDIEndTrackHandler handler)
{
Object key = new Integer(playerMIDIHandle);
if(handler == null)
{
playerHandlers.remove(key);
return;
}
playerHandlers.put(key, handler);
}
public void setThreadProxy(ThreadTerminationListener threadProxy)
{
this.threadProxy = threadProxy;
}
public void setEventsProxy(AppProxy eventsProxy)
{
this.eventsProxy = eventsProxy;
}
public void setEvent(Runnable event)
{
Object monitor;
if(event == null)
{
return;
}
synchronized(monitor = keyPressed)
{
mainEvents.push(-1L, event);
monitor.notify();
}
}
public void showMessage(String message)
{
int i;
int fontHandle;
int fontHeight;
int fontBaseline;
int textWidth;
int bkcolor;
int top;
int left;
int width;
int height;
int x;
int y;
StretchDrawDescriptor s;
StringDrawDescriptor t;
if(message == null)
{
throw new NullPointerException("Run.showMessage: " +
"параметр message равен нулевой ссылке.");
}
fontHandle = (int) MalikSystem.syscall(0L, 0x0028);
fontHeight = ((i = (int) MalikSystem.syscall((long) fontHandle, 0x002a)) >> 8) & 0xff;
fontBaseline = (i >> 16) & 0xff;
bkcolor = 0xc0000000;
s = stretchDescriptor;
MalikSystem.syscall(((long) (t = stringDescriptor).getDescriptorAddress()) << 32, 0x0022);
top = (865 * (t.sizes >>> 16)) >> 10;
left = 0;
width = t.sizes & 0xffff;
height = fontHeight;
s.dstBase = t.base;
s.dstScan = t.scan;
s.dstSizes = t.sizes;
s.dstAlpha = 0;
s.origins = (top << 16) | (left & 0xffff);
s.sizes = (height << 16) | (width & 0xffff);
s.srcBase = MalikSystem.getLocalVariableAddress(bkcolor);
s.srcScan = 1;
s.srcSizes = 0x00010001;
s.srcAlpha = 1;
s.transform = 0;
MalikSystem.syscall((long) s.getDescriptorAddress(), 0x0027);
t.alpha = 0;
t.handle = fontHandle;
t.style = Integer.MIN_VALUE;
t.color = 0xffffffff;
t.length = message.length();
t.chars = Array.getFirstElementAddress(message);
textWidth = (int) MalikSystem.syscall((long) t.getDescriptorAddress(), 0x002c);
x = (width - textWidth) >> 1;
y = top + fontHeight - fontBaseline;
t.coords = (y << 16) | (x & 0xffff);
MalikSystem.syscall((long) t.getDescriptorAddress(), 0x002d);
MalikSystem.syscall(0L, 0x0020);
}
public void terminate()
{
Object monitor;
synchronized(monitor = keyPressed)
{
terminated = true;
mainEvents.push(((long) EVENT_APP_TERMINATE) << 60, null);
monitor.notify();
}
}
public boolean terminated()
{
return terminated;
}
public Runnable[] getImmediatelyServices(Object argument)
{
int len = 0;
Runnable[] result = new ImmediatelyRunnable[0x0f];
QueueOfMalikAppEvents events = mainEvents;
Object monitor = keyPressed;
for(Runnable event; ; )
{
synchronized(monitor)
{
event = events.findImmediatelyRunnable(argument);
}
if(event == null) break;
if(len == result.length)
{
Array.copy(result, 0, result = new ImmediatelyRunnable[(len << 1) + 1], 0, len);
}
result[len++] = event;
}
if(len < result.length)
{
Array.copy(result, 0, result = new ImmediatelyRunnable[len], 0, len);
}
return result;
}
void setEvent(long event)
{
Object monitor;
synchronized(monitor = keyPressed)
{
mainEvents.push(event, null);
monitor.notify();
}
}
void setEvent(Thread terminatedThread, Throwable exitThrowable)
{
Object monitor;
synchronized(monitor = keyPressed)
{
threadTerminations.push(new ThreadTerminationRecord(this,
terminatedThread, exitThrowable));
monitor.notify();
}
}
void handlePlayerPCMLoadBlock(int playerPCMHandle, int blockIndex)
{
((PlayerPCMLoadBlockHandler) playerHandlers.get(new Integer(playerPCMHandle))).
loadPCMBlock(playerPCMHandle, blockIndex);
}
void handlePlayerMIDILoadBlock(int playerMIDIHandle)
{
((PlayerMIDIEndTrackHandler) playerHandlers.get(new Integer(playerMIDIHandle))).
endMIDITrack(playerMIDIHandle);
}
private void execute()
{
Throwable exitThrowable;
ThreadTerminationListener listener;
try
{
ThrowableStackTrace.enable(true);
showMessage("Пожалуйста, подождите…");
findAppProxy();
registerInterruptHandlers();
lifecycle();
exitThrowable = null;
}
catch(Throwable t)
{
exitThrowable = t;
}
MalikInterrupt.disable();
try
{
((listener = threadProxy) != null ? listener : this).
threadTerminated(Thread.currentThread(), exitThrowable);
}
catch(Throwable t)
{
/* проигнорировать исключение */
}
System.out.close();
System.err.close();
}
private void lifecycle()
{
long eventFromUser;
Runnable eventFromApp;
ThreadTerminationRecord eventTermination;
QueueOfObjects queueOfTerminations = threadTerminations;
QueueOfMalikAppEvents queueOfEvents = mainEvents;
Object monitor = keyPressed;
do
{
waitEvent();
try
{
if(!queueOfTerminations.isEmpty())
{
synchronized(monitor)
{
eventTermination = (ThreadTerminationRecord) queueOfTerminations.peek();
queueOfTerminations.removeTailElement();
}
eventTermination.invokeListener(threadProxy);
continue;
}
if(!queueOfEvents.isEmpty())
{
synchronized(monitor)
{
eventFromApp = queueOfEvents.peekEventFromApp();
eventFromUser = queueOfEvents.peekEventFromUser();
queueOfEvents.removeTailElement();
}
if(eventFromApp == null)
{
if(handleEventMustTerminated(eventFromUser))
{
break;
}
} else
{
eventFromApp.run();
}
continue;
}
}
catch(RuntimeException e)
{
e.printRealStackTrace();
}
} while(true);
}
private void waitEvent()
{
Object monitor;
synchronized(monitor = keyPressed)
{
do
{
try
{
monitor.wait();
break;
}
catch(InterruptedException e)
{
e.printRealStackTrace();
}
} while(true);
}
}
private void findAppProxy()
throws AppProxyNotFoundException
{
eventsProxy = getStartAppProxyInstance();
setEvent(((long) EVENT_APP_LAUNCH) << 60);
}
private void registerInterruptHandlers()
{
MalikInterrupt.register(0x10, new InterruptHandlerForKeyboard(this));
MalikInterrupt.register(0x11, new InterruptHandlerForPointer(this));
MalikInterrupt.register(0x12, new InterruptHandlerForWindowShowHide(this));
MalikInterrupt.register(0x13, new InterruptHandlerForWindowResize(this));
MalikInterrupt.register(0x1e, new InterruptHandlerForPlayerPCM(this));
MalikInterrupt.register(0x1f, new InterruptHandlerForPlayerMIDI(this));
Runtime.getRuntime().startInterruptHandling();
}
private boolean handleEventMustTerminated(long event)
{
int e = (int) (event >>> 60);
int i;
boolean[] k;
AppProxy p;
if((p = eventsProxy) == null)
{
return e == EVENT_APP_TERMINATE;
}
switch(e)
{
default:
return false;
case EVENT_APP_LAUNCH:
p.appLaunch();
return false;
case EVENT_APP_TERMINATE:
p.appTerminate();
return true;
case EVENT_KEY_PRESSED:
if(!(k = keyPressed)[i = (int) event])
{
k[i] = true;
p.keyPressed(i, ((int) (event >> 32)) & 0x00ffffff);
return false;
}
p.keyRepeated(i, ((int) (event >> 32)) & 0x00ffffff);
return false;
case EVENT_KEY_RELEASED:
keyPressed[i = (int) event] = false;
p.keyReleased(i);
return false;
case EVENT_POINTER_PRESSED:
p.pointerPressed((short) event, ((int) event) >> 16, ((int) (event >> 32)) & 0x07);
return false;
case EVENT_POINTER_DRAGGED:
p.pointerDragged((short) event, ((int) event) >> 16);
return false;
case EVENT_POINTER_RELEASED:
p.pointerReleased((short) event, ((int) event) >> 16, ((int) (event >> 32)) & 0x07);
return false;
case EVENT_WINDOW_RESIZE:
p.windowResize((short) event, ((int) event) >> 16);
return false;
case EVENT_WINDOW_SHOW:
p.windowShow();
return false;
case EVENT_WINDOW_HIDE:
p.windowHide();
return false;
}
}
}