RecordStore.java

Переключить прокрутку окна
Загрузить этот исходный код

/*
    Реализация спецификаций 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 javax.microedition.rms;

import java.io.*;
import java.util.*;
import malik.emulator.io.cloud.*;
import malik.emulator.io.vfs.*;
import malik.emulator.util.*;

public class RecordStore extends Object
{
    private static final class DefaultFilterComparator extends Object implements RecordFilter, RecordComparator
    {
        public DefaultFilterComparator() {
        }

        public boolean matches(byte[] recordData) {
            return true;
        }

        public int compare(byte[] recordData1, byte[] recordData2) {
            return EQUIVALENT;
        }
    }

    private static final char HEX_PREFIX = '$';
    public static final int AUTHMODE_PRIVATE = 0;
    public static final int AUTHMODE_ANY     = 1;
    public static final int STATE_ERROR  = -1;
    public static final int STATE_RESERV =  0;
    public static final int STATE_OPENED =  1;
    public static final int STATE_CLOSED =  2;
    private static final int NAME_MAXIMUM_LENGTH = 32;
    private static final long RMS_SIGNATURE = 0x6d6964702d726d73L;
    private static final String RMS_DIR_NAME = "/rms";
    private static final String RMS_DIR_PATH = RMS_DIR_NAME + "/";
    private static final String RMS_EXTENSION = ".rms";

    private static RecordStore[] OPENED;
    private static final RecordFilter DEFAULT_FILTER;
    private static final RecordComparator DEFAULT_COMPARATOR;
    static final Object MONITOR;

    static {
        DefaultFilterComparator ref = new DefaultFilterComparator();
        OPENED = new RecordStore[2];
        DEFAULT_FILTER = ref;
        DEFAULT_COMPARATOR = ref;
        MONITOR = ref;
    }

    public static void closeAllRecordStores() {
        synchronized(MONITOR)
        {
            for(int i = OPENED.length; i-- > 0; )
            {
                RecordStore store;
                if((store = OPENED[i]) != null && store.openedCount != 0)
                {
                    if(store.saveData())
                    {
                        store.openedCount = 0;
                        store.close();
                        continue;
                    }
                    System.out.println((new StringBuilder()).append("/!\\ RecordStore.closeAllRecordStores: не удалось сохранить хранилище записей ").append(store.name).append(".").toString());
                }
            }
        }
    }

    public static void deleteRecordStore(String name) throws RecordStoreException {
        int error;
        checkName("deleteRecordStore", name);
        error = 0;
        synchronized(MONITOR)
        {
            label0:
            {
                int i;
                if((i = indexOfRecordStore(name)) >= 0)
                {
                    if(OPENED[i].openedCount != 0)
                    {
                        error = 1;
                        break label0;
                    }
                    OPENED[i] = null;
                }
            }
        }
        if(error == 1)
        {
            throw new RecordStoreException((new StringBuilder()).append("RecordStore.deleteRecordStore: невозможно удалить открытое хранилище записей ").append(name).append(".").toString());
        }
        try
        {
            CloudFileSystem.instance.deleteFile(toFileName(name));
        }
        catch(IOException e)
        {
            throw new RecordStoreNotFoundException((new StringBuilder()).append("RecordStore.deleteRecordStore: не удалось найти хранилище записей ").append(name).append(".").toString());
        }
    }

    public static int getState(String name) {
        int i;
        RecordStore store;
        return
            name == null || (i = name.length()) < 1 || i > NAME_MAXIMUM_LENGTH ? STATE_ERROR :
            (store = getRecordStore(name)) == null ? STATE_RESERV :
            store.openedCount != 0 ? STATE_OPENED : STATE_CLOSED
        ;
    }

    public static String[] listRecordStores() {
        int i;
        int extlen = RMS_EXTENSION.length();
        String[] result;
        Vector list = new Vector(i = OPENED.length);
        while(i-- > 0)
        {
            RecordStore store;
            if((store = OPENED[i]) != null) list.addElement(store.name);
        }
        try
        {
            FileEnumeration e;
            if((e = CloudFileSystem.instance.findFirst(RMS_DIR_PATH)) != null)
            {
                try
                {
                    do
                    {
                        int len;
                        String n;
                        if(
                            !e.isDirectory() && (n = e.getName()) != null && n.regionMatches(true, len = n.length() - extlen, RMS_EXTENSION, 0, extlen) &&
                            isStoreName(n = n.substring(0, len)) && !list.contains(n = toStoreName(n))
                        ) list.addElement(n);
                    } while(e.findNext());
                }
                finally
                {
                    e.close();
                }
            }
        }
        catch(IOException e)
        {
            e.printRealStackTrace();
        }
        list.copyInto(result = new String[list.size()]);
        return result;
    }

    public static RecordStore openRecordStore(String name, boolean createIfNecessary) throws RecordStoreException {
        int error;
        RecordStore result;
        checkName("openRecordStore", name);
        error = 0;
        synchronized(MONITOR)
        {
            label0:
            {
                int i;
                if((i = indexOfRecordStore(name)) >= 0)
                {
                    if((i = (result = OPENED[i]).openedCount) == 0 && !result.loadData() && !createIfNecessary)
                    {
                        error = 1;
                        break label0;
                    }
                    result.openedCount = i + 1;
                } else
                {
                    if(!(result = new RecordStore(name)).loadData() && !createIfNecessary)
                    {
                        error = 2;
                        break label0;
                    }
                    register(result);
                    result.openedCount = 1;
                }
            }
        }
        switch(error)
        {
        case 1:
            throw new RecordStoreException((new StringBuilder()).append("RecordStore.openRecordStore: не удалось загрузить хранилище записей ").append(name).append(".").toString());
        case 2:
            throw new RecordStoreNotFoundException((new StringBuilder()).append("RecordStore.openRecordStore: не удалось найти хранилище записей ").append(name).append(".").toString());
        }
        return result;
    }

    public static RecordStore openRecordStore(String name, boolean createIfNecessary, int authmode, boolean writable) throws RecordStoreException {
        return openRecordStore(name, createIfNecessary);
    }

    public static RecordStore openRecordStore(String name, String vendorName, String suiteName) throws RecordStoreException {
        return openRecordStore(name, false);
    }

    private static void checkName(String method, String name) {
        int len;
        if(name == null || (len = name.length()) < 1 || len > NAME_MAXIMUM_LENGTH)
        {
            throw new IllegalArgumentException((new StringBuilder()).append("RecordStore.").append(method).append(": недопустимое имя хранилища записей.").toString());
        }
    }

    private static void register(RecordStore store) {
        int i;
        int len;
        if((i = Array.findb(OPENED, (len = OPENED.length) - 1, null)) >= 0)
        {
            OPENED[i] = store;
            return;
        }
        Array.copy(OPENED, 0, OPENED = new RecordStore[len << 1], 0, len);
        OPENED[len] = store;
    }

    private static boolean isStoreName(String name) {
        int len;
        char[] chars;
        name.getChars(0, len = name.length(), chars = new char[len + 1], 0);
        for(int i = 0; i < len; )
        {
            char c;
            char code;
            if((c = chars[i++]) != HEX_PREFIX)
            {
                if(c == '_' || c >= '0' && c <= '9' || c >= 'a' && c <= 'z') continue;
                return false;
            }
            code = 0;
            for(int j = 4; j-- > 0; )
            {
                if((c = chars[i++]) >= '0' && c <= '9')
                {
                    code = (char) ((code << 4) | (c - '0'));
                    continue;
                }
                if(c >= 'A' && c <= 'F')
                {
                    code = (char) ((code << 4) | (c - ('A' - 0x0a)));
                    continue;
                }
                return false;
            }
            if((c = code) == '_' || c >= '0' && c <= '9' || c >= 'a' && c <= 'z') return false;
        }
        return true;
    }

    private static char getDigitRepresentation(int digit) {
        if(digit >= 0x00 && digit < 0x0a) return (char) (digit + '0');
        if(digit >= 0x0a && digit < 0x10) return (char) (digit + ('A' - 0x0a));
        throw new ArrayIndexOutOfBoundsException(digit);
    }

    private static int indexOfRecordStore(String name) {
        int i;
        RecordStore store;
        for(i = OPENED.length; i-- > 0; ) if((store = OPENED[i]) != null && name.equals(store.name)) return i;
        return -1;
    }

    private static RecordStore getRecordStore(String name) {
        int i;
        RecordStore store;
        for(i = OPENED.length; i-- > 0; ) if((store = OPENED[i]) != null && name.equals(store.name)) return store;
        return null;
    }

    private static String toStoreName(String fileName) {
        int length = 0;
        char[] result = new char[NAME_MAXIMUM_LENGTH];
        for(int len = fileName.length(), i = 0; i < len && length < NAME_MAXIMUM_LENGTH; )
        {
            char c;
            if((c = fileName.charAt(i++)) == HEX_PREFIX)
            {
                int j = 0;
                c = 0;
                do
                {
                    char d;
                    if(i >= len) break;
                    if((d = fileName.charAt(i++)) >= '0' && d <= '9')
                    {
                        c = (char) ((c << 4) | (d - '0'));
                        continue;
                    }
                    if(d >= 'A' && d <= 'F')
                    {
                        c = (char) ((c << 4) | (d - ('A' - 0x0a)));
                        continue;
                    }
                    break;
                } while(++j < 4);
            }
            result[length++] = c;
        }
        return new String(result, 0, length);
    }

    private static String toFileName(String name) {
        int length = 0;
        char[] result = new char[5 * NAME_MAXIMUM_LENGTH];
        for(int len = name.length(), i = 0; i < len; )
        {
            char c;
            if((c = name.charAt(i++)) == '_' || c >= '0' && c <= '9' || c >= 'a' && c <= 'z')
            {
                result[length++] = c;
                continue;
            }
            result[length++] = HEX_PREFIX;
            result[length++] = getDigitRepresentation((c >> 0x0c) & 0x0f);
            result[length++] = getDigitRepresentation((c >> 0x08) & 0x0f);
            result[length++] = getDigitRepresentation((c >> 0x04) & 0x0f);
            result[length++] = getDigitRepresentation(c & 0x0f);
        }
        return (new StringBuilder()).append(RMS_DIR_PATH).append(result, 0, length).append(RMS_EXTENSION).toString();
    }

    private final class Enumerator extends Object implements RecordListener, RecordEnumeration
    {
        private boolean nowCreated;
        private boolean autoRebuild;
        private int current;
        private int count;
        private Record[] records;
        private final RecordComparator comparator;
        private final RecordFilter filter;
        private final Object monitor;

        public Enumerator(RecordFilter filter, RecordComparator comparator, boolean autoRebuild) {
            this.nowCreated = true;
            this.autoRebuild = autoRebuild;
            this.comparator = comparator;
            this.filter = filter;
            this.monitor = new Object();
            rebuild();
            if(autoRebuild) (RecordStore.this).addRecordListener(this);
        }

        public void recordAdded(RecordStore store, int recordID) {
            if(store == RecordStore.this) rebuild();
        }

        public void recordChanged(RecordStore store, int recordID) {
            if(store == RecordStore.this) rebuild();
        }

        public void recordDeleted(RecordStore store, int recordID) {
            if(store == RecordStore.this) rebuild();
        }

        public void destroy() {
        }

        public void rebuild() {
            boolean opened;
            int sourceCount;
            int rebuildedCount;
            Record[] sourceRecs;
            Record[] rebuildedRecs;
            RecordComparator comparator = this.comparator;
            RecordFilter filter = this.filter;
            RecordStore store = RecordStore.this;
            synchronized(RecordStore.MONITOR)
            {
                if(opened = store.openedCount != 0)
                {
                    sourceCount = store.count;
                    sourceRecs = store.records;
                } else
                {
                    sourceCount = 0;
                    sourceRecs = null;
                }
            }
            if(sourceRecs == null || !opened) return;
            rebuildedCount = 0;
            rebuildedRecs = new Record[sourceCount];
            try
            {
                for(int i = 0; i < sourceCount; i++)
                {
                    Record rec;
                    if((rec = sourceRecs[i]) != null && filter.matches(rec.getData())) rebuildedRecs[rebuildedCount++] = rec;
                }
                for(int lim = rebuildedCount - 1, i = 0; i < lim; i++)
                {
                    Record rec1 = rebuildedRecs[i];
                    for(int j = i + 1; j < rebuildedCount; j++)
                    {
                        Record rec2 = rebuildedRecs[j];
                        if(comparator.compare(rec2.getData(), rec1.getData()) < 0)
                        {
                            rebuildedRecs[i] = rec2;
                            rebuildedRecs[j] = rec1;
                            rec1 = rec2;
                        }
                    }
                }
            }
            catch(RuntimeException e)
            {
                e.printRealStackTrace();
                return;
            }
            synchronized(monitor)
            {
                nowCreated = true;
                count = rebuildedCount;
                records = rebuildedRecs;
            }
        }

        public void reset() {
            synchronized(monitor)
            {
                nowCreated = true;
            }
        }

        public void keepUpdated(boolean autoRebuild) {
            synchronized(monitor)
            {
                if(this.autoRebuild != autoRebuild)
                {
                    if(this.autoRebuild = autoRebuild)
                    {
                        (RecordStore.this).addRecordListener(this);
                    } else
                    {
                        (RecordStore.this).removeRecordListener(this);
                    }
                }
            }
            if(autoRebuild) rebuild();
        }

        public boolean isKeptUpdated() {
            return autoRebuild;
        }

        public boolean hasNextElement() {
            boolean result;
            synchronized(monitor)
            {
                result = nowCreated ? count > 0 : current < count - 1;
            }
            return result;
        }

        public boolean hasPreviousElement() {
            boolean result;
            synchronized(monitor)
            {
                result = nowCreated ? count > 0 : current > 0;
            }
            return result;
        }

        public int numRecords() {
            return count;
        }

        public int nextRecordId() throws InvalidRecordIDException {
            int error = 0;
            int result;
            synchronized(monitor)
            {
                label0:
                {
                    int index;
                    if((index = toNextRecord()) < 0)
                    {
                        error = 1;
                        result = 0;
                        break label0;
                    }
                    result = records[index].getID();
                }
            }
            if(error == 1)
            {
                throw new InvalidRecordIDException("RecordEnumeration.nextRecordId: в перечислении больше не осталось записей.");
            }
            return result;
        }

        public int previousRecordId() throws InvalidRecordIDException {
            int error = 0;
            int result;
            synchronized(monitor)
            {
                label0:
                {
                    int index;
                    if((index = toPrevRecord()) < 0)
                    {
                        error = 1;
                        result = 0;
                        break label0;
                    }
                    result = records[index].getID();
                }
            }
            if(error == 1)
            {
                throw new InvalidRecordIDException("RecordEnumeration.previousRecordId: в перечислении больше не осталось записей.");
            }
            return result;
        }

        public byte[] nextRecord() throws RecordStoreException {
            int error = 0;
            byte[] result;
            RecordStore store;
            synchronized(monitor)
            {
                label0:
                {
                    int len;
                    int index;
                    if((store = RecordStore.this).openedCount == 0)
                    {
                        error = 1;
                        result = null;
                        break label0;
                    }
                    if((index = toNextRecord()) < 0)
                    {
                        error = 2;
                        result = null;
                        break label0;
                    }
                    Array.copy(result = records[index].getData(), 0, result = new byte[len = result.length], 0, len);
                }
            }
            switch(error)
            {
            case 1:
                throw new RecordStoreNotOpenException((new StringBuilder()).append("RecordEnumeration.nextRecord: хранилище записей ").append(store.name).append(" не открыто.").toString());
            case 2:
                throw new InvalidRecordIDException("RecordEnumeration.nextRecord: в перечислении больше не осталось записей.");
            }
            return result;
        }

        public byte[] previousRecord() throws RecordStoreException {
            int error = 0;
            byte[] result;
            RecordStore store;
            synchronized(monitor)
            {
                label0:
                {
                    int len;
                    int index;
                    if((store = RecordStore.this).openedCount == 0)
                    {
                        error = 1;
                        result = null;
                        break label0;
                    }
                    if((index = toPrevRecord()) < 0)
                    {
                        error = 2;
                        result = null;
                        break label0;
                    }
                    Array.copy(result = records[index].getData(), 0, result = new byte[len = result.length], 0, len);
                }
            }
            switch(error)
            {
            case 1:
                throw new RecordStoreNotOpenException((new StringBuilder()).append("RecordEnumeration.previousRecord: хранилище записей ").append(store.name).append(" не открыто.").toString());
            case 2:
                throw new InvalidRecordIDException("RecordEnumeration.previousRecord: в перечислении больше не осталось записей.");
            }
            return result;
        }

        private int toNextRecord() {
            int cur;
            if(nowCreated)
            {
                if(count <= 0) return -1;
                nowCreated = false;
                return current = 0;
            }
            return (cur = current) >= count - 1 ? -1 : (current = cur + 1);
        }

        private int toPrevRecord() {
            int cur;
            if(nowCreated)
            {
                int cnt;
                if((cnt = count) <= 0) return -1;
                nowCreated = false;
                return current = cnt - 1;
            }
            return (cur = current) <= 0 ? -1 : (current = cur - 1);
        }
    }

    int openedCount;
    private int version;
    private int nextID;
    int count;
    private long lastModified;
    Record[] records;
    private RecordListener[] listeners;
    final String name;

    private RecordStore(String name) {
        this.name = name;
    }

    public void addRecordListener(RecordListener listener) {
        if(listener == null) return;
        synchronized(MONITOR)
        {
            int len;
            RecordListener[] listeners;
            if((listeners = this.listeners) == null) listeners = this.listeners = new RecordListener[3];
            if(Array.findf(listeners, 0, listener) >= (len = listeners.length))
            {
                int index;
                if((index = Array.findf(listeners, 0, null)) >= len)
                {
                    Array.copy(listeners, 0, listeners = new RecordListener[(len << 1) + 1], 0, len);
                    this.listeners = listeners;
                }
                listeners[index] = listener;
            }
        }
    }

    public void removeRecordListener(RecordListener listener) {
        if(listener == null) return;
        synchronized(MONITOR)
        {
            int index;
            RecordListener[] listeners;
            if((listeners = this.listeners) != null && (index = Array.findf(listeners, 0, listener)) < listeners.length) listeners[index] = null;
        }
    }

    public void closeRecordStore() throws RecordStoreException {
        int error = 0;
        synchronized(MONITOR)
        {
            label0:
            {
                int i;
                if((i = openedCount) == 0)
                {
                    error = 1;
                    break label0;
                }
                if((openedCount = i - 1) == 0)
                {
                    if(!saveData())
                    {
                        error = 2;
                        break label0;
                    }
                    close();
                }
            }
        }
        switch(error)
        {
        case 1:
            throw new RecordStoreNotOpenException((new StringBuilder()).append("RecordStore.closeRecordStore: невозможно закрыть уже закрытое хранилище записей ").append(name).append(".").toString());
        case 2:
            throw new RecordStoreException((new StringBuilder()).append("RecordStore.closeRecordStore: не удалось сохранить хранилище записей ").append(name).append(".").toString());
        }
    }

    public void deleteRecord(int recordID) throws RecordStoreException {
        int error = 0;
        synchronized(MONITOR)
        {
            label0:
            {
                int len;
                int index;
                Record[] recs;
                if(openedCount == 0)
                {
                    error = 1;
                    break label0;
                }
                if((index = indexOfRecord(recordID)) < 0)
                {
                    error = 2;
                    break label0;
                }
                recs = records;
                if((len = count - 1) > index) Array.copy(recs, index + 1, recs, index, len - index);
                recs[count = len] = null;
                version++;
                lastModified = System.currentTimeMillis();
            }
        }
        switch(error)
        {
        case 1:
            throw new RecordStoreNotOpenException((new StringBuilder()).append("RecordStore.deleteRecord: хранилище записей ").append(name).append(" не открыто.").toString());
        case 2:
            throw new InvalidRecordIDException((new StringBuilder()).append("RecordStore.deleteRecord: неизвестный идентификатор записи (").append(recordID).append(").").toString());
        }
        notifyListenersDeleted(recordID);
    }

    public void setMode(int authmode, boolean writable) throws RecordStoreException {
        if(authmode != AUTHMODE_ANY && authmode != AUTHMODE_PRIVATE)
        {
            throw new IllegalArgumentException("RecordStore.setMode: аргумент authmode имеет недопустимое значение.");
        }
    }

    public void setRecord(int recordID, byte[] src, int offset, int length) throws RecordStoreException {
        int error;
        if(src != null) Array.checkBound("RecordStore.setRecord", src.length, offset, length);
        error = 0;
        synchronized(MONITOR)
        {
            label0:
            {
                int index;
                if(openedCount == 0)
                {
                    error = 1;
                    break label0;
                }
                if((index = indexOfRecord(recordID)) < 0)
                {
                    error = 2;
                    break label0;
                }
                if(src != null)
                {
                    Array.copy(src, offset, src = new byte[length], 0, length);
                } else
                {
                    src = new byte[0];
                }
                records[index].setData(src);
                version++;
                lastModified = System.currentTimeMillis();
            }
        }
        switch(error)
        {
        case 1:
            throw new RecordStoreNotOpenException((new StringBuilder()).append("RecordStore.setRecord: хранилище записей ").append(name).append(" не открыто.").toString());
        case 2:
            throw new InvalidRecordIDException((new StringBuilder()).append("RecordStore.setRecord: неизвестный идентификатор записи (").append(recordID).append(").").toString());
        }
        notifyListenersChanged(recordID);
    }

    public int addRecord(byte[] src, int offset, int length) throws RecordStoreException {
        int error;
        int result;
        if(src != null) Array.checkBound("RecordStore.addRecord", src.length, offset, length);
        error = 0;
        synchronized(MONITOR)
        {
            label0:
            {
                int len;
                Record[] recs;
                if(openedCount == 0)
                {
                    error = 1;
                    result = 0;
                    break label0;
                }
                if((recs = records) == null || recs.length <= 0) recs = records = new Record[3];
                if((len = count) == recs.length) Array.copy(recs, 0, recs = records = new Record[(len << 1) + 1], 0, len);
                if(src != null)
                {
                    Array.copy(src, offset, src = new byte[length], 0, length);
                } else
                {
                    src = new byte[0];
                }
                recs[len] = new Record(result = nextID++, src);
                count = len + 1;
                version++;
                lastModified = System.currentTimeMillis();
            }
        }
        if(error == 1)
        {
            throw new RecordStoreNotOpenException((new StringBuilder()).append("RecordStore.addRecord: хранилище записей ").append(name).append(" не открыто.").toString());
        }
        notifyListenersAdded(result);
        return result;
    }

    public int getRecord(int recordID, byte[] dst, int offset) throws RecordStoreException {
        int error;
        int result;
        if(dst == null)
        {
            throw new NullPointerException("RecordStore.getRecord: аргумент dst равен нулевой ссылке.");
        }
        error = 0;
        synchronized(MONITOR)
        {
            label0:
            {
                int index;
                byte[] data;
                if(openedCount == 0)
                {
                    error = 1;
                    result = 0;
                    break label0;
                }
                if((index = indexOfRecord(recordID)) < 0)
                {
                    error = 2;
                    result = 0;
                    break label0;
                }
                if(!Array.isBoundValid(dst.length, offset, result = (data = records[index].getData()).length))
                {
                    error = 3;
                    result = 0;
                    break label0;
                }
                Array.copy(data, 0, dst, offset, result);
            }
        }
        switch(error)
        {
        case 1:
            throw new RecordStoreNotOpenException((new StringBuilder()).append("RecordStore.getRecord: хранилище записей ").append(name).append(" не открыто.").toString());
        case 2:
            throw new InvalidRecordIDException((new StringBuilder()).append("RecordStore.getRecord: неизвестный идентификатор записи (").append(recordID).append(").").toString());
        case 3:
            throw new ArrayIndexOutOfBoundsException("RecordStore.getRecord: индекс выходит из диапазона.");
        }
        return result;
    }

    public int getRecordSize(int recordID) throws RecordStoreException {
        int error = 0;
        int result;
        synchronized(MONITOR)
        {
            label0:
            {
                int index;
                if(openedCount == 0)
                {
                    error = 1;
                    result = 0;
                    break label0;
                }
                if((index = indexOfRecord(recordID)) < 0)
                {
                    error = 2;
                    result = 0;
                    break label0;
                }
                result = records[index].getSize();
            }
        }
        switch(error)
        {
        case 1:
            throw new RecordStoreNotOpenException((new StringBuilder()).append("RecordStore.getRecordSize: хранилище записей ").append(name).append(" не открыто.").toString());
        case 2:
            throw new InvalidRecordIDException((new StringBuilder()).append("RecordStore.getRecordSize: неизвестный идентификатор записи (").append(recordID).append(").").toString());
        }
        return result;
    }

    public int getNextRecordID() throws RecordStoreException {
        if(openedCount == 0)
        {
            throw new RecordStoreNotOpenException((new StringBuilder()).append("RecordStore.getNextRecordID: хранилище записей ").append(name).append(" не открыто.").toString());
        }
        return nextID;
    }

    public int getNumRecords() throws RecordStoreNotOpenException {
        if(openedCount == 0)
        {
            throw new RecordStoreNotOpenException((new StringBuilder()).append("RecordStore.getNumRecords: хранилище записей ").append(name).append(" не открыто.").toString());
        }
        return count;
    }

    public int getVersion() throws RecordStoreNotOpenException {
        if(openedCount == 0)
        {
            throw new RecordStoreNotOpenException((new StringBuilder()).append("RecordStore.getVersion: хранилище записей ").append(name).append(" не открыто.").toString());
        }
        return version;
    }

    public int getSize() throws RecordStoreNotOpenException {
        int error = 0;
        int result;
        synchronized(MONITOR)
        {
            label0:
            {
                Record[] recs;
                if(openedCount == 0)
                {
                    error = 1;
                    result = 0;
                    break label0;
                }
                result = 0x20;
                recs = records;
                for(int i = count; i-- > 0; )
                {
                    result += recs[i].getSize() + 0x08;
                    result += -result & 0x03;
                }
            }
        }
        if(error == 1)
        {
            throw new RecordStoreNotOpenException((new StringBuilder()).append("RecordStore.getSize: хранилище записей ").append(name).append(" не открыто.").toString());
        }
        return result;
    }

    public int getSizeAvailable() throws RecordStoreNotOpenException {
        if(openedCount == 0)
        {
            throw new RecordStoreNotOpenException((new StringBuilder()).append("RecordStore.getSizeAvailable: хранилище записей ").append(name).append(" не открыто.").toString());
        }
        return Memory.getFree();
    }

    public long getLastModified() throws RecordStoreNotOpenException {
        if(openedCount == 0)
        {
            throw new RecordStoreNotOpenException((new StringBuilder()).append("RecordStore.getLastModified: хранилище записей ").append(name).append(" не открыто.").toString());
        }
        return lastModified;
    }

    public byte[] getRecord(int recordID) throws RecordStoreException {
        int error = 0;
        byte[] result;
        synchronized(MONITOR)
        {
            label0:
            {
                int len;
                int index;
                byte[] data;
                if(openedCount == 0)
                {
                    error = 1;
                    result = null;
                    break label0;
                }
                if((index = indexOfRecord(recordID)) < 0)
                {
                    error = 2;
                    result = null;
                    break label0;
                }
                Array.copy(data = records[index].getData(), 0, result = new byte[len = data.length], 0, len);
            }
        }
        switch(error)
        {
        case 1:
            throw new RecordStoreNotOpenException((new StringBuilder()).append("RecordStore.getRecord: хранилище записей ").append(name).append(" не открыто.").toString());
        case 2:
            throw new InvalidRecordIDException((new StringBuilder()).append("RecordStore.getRecord: неизвестный идентификатор записи (").append(recordID).append(").").toString());
        }
        return result;
    }

    public String getName() throws RecordStoreNotOpenException {
        return name;
    }

    public RecordEnumeration enumerateRecords(RecordFilter filter, RecordComparator comparator, boolean autoRebuild) throws RecordStoreNotOpenException {
        if(openedCount == 0)
        {
            throw new RecordStoreNotOpenException((new StringBuilder()).append("RecordStore.enumerateRecords: хранилище записей ").append(name).append(" не открыто.").toString());
        }
        return this.new Enumerator(filter == null ? DEFAULT_FILTER : filter, comparator == null ? DEFAULT_COMPARATOR : comparator, autoRebuild);
    }

    private void notifyListenersAdded(int recordID) {
        RecordListener[] listeners;
        for(int i = (listeners = this.listeners) == null ? 0 : listeners.length; i-- > 0; )
        {
            RecordListener listener;
            if((listener = listeners[i]) != null)
            {
                try
                {
                    listener.recordAdded(this, recordID);
                }
                catch(RuntimeException e)
                {
                    e.printRealStackTrace();
                }
            }
        }
    }

    private void notifyListenersChanged(int recordID) {
        RecordListener[] listeners;
        for(int i = (listeners = this.listeners) == null ? 0 : listeners.length; i-- > 0; )
        {
            RecordListener listener;
            if((listener = listeners[i]) != null)
            {
                try
                {
                    listener.recordChanged(this, recordID);
                }
                catch(RuntimeException e)
                {
                    e.printRealStackTrace();
                }
            }
        }
    }

    private void notifyListenersDeleted(int recordID) {
        RecordListener[] listeners;
        for(int i = (listeners = this.listeners) == null ? 0 : listeners.length; i-- > 0; )
        {
            RecordListener listener;
            if((listener = listeners[i]) != null)
            {
                try
                {
                    listener.recordDeleted(this, recordID);
                }
                catch(RuntimeException e)
                {
                    e.printRealStackTrace();
                }
            }
        }
    }

    private void close() {
        count = 0;
        records = null;
        listeners = null;
        System.out.println((new StringBuilder()).append("RecordStore: внесены изменения в запись ").append(name).append(".").toString());
    }

    private boolean saveData() {
        boolean result;
        HandleOutputStream file;
        try
        {
            try
            {
                CloudFileSystem.instance.readAttributes(RMS_DIR_NAME, null);
            }
            catch(IOException e)
            {
                CloudFileSystem.instance.createDirectory(RMS_DIR_NAME);
            }
        }
        catch(IOException e)
        {
        }
        if((file = new FileOutputStream(toFileName(name))).hasOpenError()) return false;
        try
        {
            try
            {
                int len = count;
                Record[] recs = records;
                DataOutputStream stream;
                (stream = new DataOutputStream(file)).writeLong(RMS_SIGNATURE);
                stream.writeInt(len);
                stream.writeInt(version);
                stream.writeLong(lastModified);
                stream.writeInt(nextID);
                stream.writeInt(0);
                for(int i = 0; i < len; i++)
                {
                    int size;
                    Record rec = recs[i];
                    stream.writeInt(rec.getID());
                    stream.writeInt(size = rec.getSize());
                    stream.write(rec.getData());
                    switch(size & 3)
                    {
                    case 1:
                        stream.writeByte(0);
                        /* fall through */
                    case 2:
                        stream.writeByte(0);
                        /* fall through */
                    case 3:
                        stream.writeByte(0);
                        /* fall through */
                    default:
                        break;
                    }
                }
            }
            finally
            {
                file.close();
            }
            result = true;
        }
        catch(IOException e)
        {
            e.printRealStackTrace();
            result = false;
        }
        return result;
    }

    private boolean loadData() {
        boolean result;
        HandleInputStream file;
        if((file = new FileInputStream(toFileName(name))).hasOpenError())
        {
            version = 1;
            nextID = 1;
            count = 0;
            lastModified = System.currentTimeMillis();
            records = null;
            return false;
        }
        try
        {
            try
            {
                DataInputStream stream;
                if((stream = new DataInputStream(file)).readLong() != RMS_SIGNATURE)
                {
                    version = 1;
                    nextID = 1;
                    count = 0;
                    lastModified = System.currentTimeMillis();
                    records = null;
                    result = false;
                } else
                {
                    int len;
                    Record[] recs;
                    count = len = stream.readInt();
                    version = stream.readInt();
                    lastModified = stream.readLong();
                    nextID = stream.readInt();
                    records = recs = new Record[len];
                    stream.skipBytes(4);
                    for(int i = 0; i < len; i++)
                    {
                        int id = stream.readInt();
                        int size = stream.readInt();
                        byte[] data;
                        stream.read(data = new byte[size]);
                        if((size &= 3) != 0) stream.skipBytes(4 - size);
                        recs[i] = new Record(id, data);
                    }
                    result = true;
                }
            }
            finally
            {
                file.close();
            }
        }
        catch(IOException e)
        {
            e.printRealStackTrace();
            result = false;
        }
        return result;
    }

    private int indexOfRecord(int recordID) {
        Record[] recs = records;
        for(int i = count; i-- > 0; ) if(recs[i].getID() == recordID) return i;
        return -1;
    }
}

final class Record extends Object
{
    private final int id;
    private byte[] data;

    public Record(int id, byte[] data) {
        this.id = id;
        this.data = data;
    }

    public void setData(byte[] data) {
        this.data = data;
    }

    public int getID() {
        return id;
    }

    public int getSize() {
        return data.length;
    }

    public byte[] getData() {
        return data;
    }
}