/******************************************************************************* * This file is part of the RozkladPKP project. * * RozkladPKP is free software: you can redistribute it and/or modify * it under the terms of the GNU General Public License as published by * the Free Software Foundation, either version 3 of the License, or * (at your option) any later version. * * RozkladPKP is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU General Public License for more details. * * You should have received a copy of the GNU General Public License * along with RozkladPKP. If not, see <http://www.gnu.org/licenses/>. ******************************************************************************/ package org.tyszecki.rozkladpkp.pln; import java.io.UnsupportedEncodingException; import java.util.BitSet; import java.util.HashMap; import java.util.Iterator; import java.util.SortedSet; import java.util.TreeSet; import android.util.Log; public class PLN { public static final int DATE_GENERATED = 0; public static final int DATE_START = 1; public static final int DATE_END = 2; int attributesStart,attributesEnd; int stationsStart; int stringStart,availabilitiesStart; final int TrainSize = 20; final int StationSize = 14; private StringManager strings; private AttributeManager attributes; MessageManager messages; public UnboundConnection[] connections; private Station[] stations; private Station dep,arr; private int totalConCnt = -1; HashMap<Integer,Availability> availabilities; private DayUtils dayUtils; public int conCnt; Boolean delayInfo = null; public byte[] data; public android.text.format.Time sdate,edate,today; class StringManager{ HashMap<Integer,String> cache = new HashMap<Integer, String>(); String get(int offset) { if(cache.containsKey(offset)) return cache.get(offset); String t = getString(offset+stringStart); cache.put(offset, t); return t; } } class AttributeManager{ private HashMap<Integer,String[]> cache = new HashMap<Integer, String[]>(); String[] get(int offset) { if(cache.containsKey(offset)) return cache.get(offset); String[] t = readAttributeList(offset); cache.put(offset, t); return t; } } class MessageManager{ private final int messagesOffset; private HashMap<Integer,Message[]> cache = new HashMap<Integer, Message[]>(); private SortedSet<Integer> offsets = new TreeSet<Integer>(); public MessageManager() { messagesOffset = readShort(attributesEnd+0x16); } void addOffset(int offset) { offsets.add(offset); } Message[] get(int offset) { if(cache.containsKey(offset)) return cache.get(offset); int n = data.length; //Następny offset try{n = offsets.tailSet(offset+1).first();}catch (Exception e) {} int cnt = (n-offset)/18; Message[] t = new Message[cnt]; for(int i = 0; i < cnt; ++i) t[i] = readMessage(offset+i*18); cache.put(offset, t); return t; } //Zwraca przesunięcie komunikatów dla konkretnego połączenia public int offsetForConnection(int connectionNumber) { if(messagesOffset <= 0) return -1; int msg = readShort(messagesOffset+2*(connectionNumber+1)); if(msg > 0) { msg += messagesOffset; addOffset(msg); return msg; } return -1; } } public class Availability{ private int length; private int bsLen = -1; private int card = 0; public Availability(int m,int offset, int dayOffset, int len) { msgOffset = m; dOffset = dayOffset; days = new BitSet(len*8); int ix = 0; for(int i = 0; i < len; i++) for(int j = 7; j >= 0; j--, ix++) if(((data[offset+i] >>j) & 1) != 0) { days.set(ix); ++card; } length = len*8; } public boolean available(int day) { day -= dOffset*8; if(day >= 0 && day < length) return days.get(day); return false; } public int length() { return dOffset*8+days.size(); } public int daysCount() { return card; } public BitSet bitset() { return days; } public int offset() { return dOffset; } public int bsLength() { if(bsLen == -1) bsLen = days.length(); return bsLen; } public String getMessage() { if(msg == null) msg = strings.get(msgOffset); return msg; } private String msg; private BitSet days; int dOffset; private int msgOffset; } public class Station { int x,y; public int id; public String name; public String toString() { return name; } } public class Train { public PLNTimestamp deptime; public PLNTimestamp arrtime; private int offset; public String number; public Station depstation; public Station arrstation; private String attr[] = null; private String depplatform, arrplatform; int changeOffset = -1; private int attributesOffset; private TrainChange change; public TrainChange getChange() { if(changeOffset == -1) return null; if(change != null) return change; if(changeOffset != -1) change = readTrainChanges(changeOffset); return change; } public int getAttributeCount() { if(attr == null) attr = attributes.get(attributesOffset); return attr.length; } public String getDeparturePlatform() { if(depplatform == null) depplatform = strings.get(readShort(offset+12)); return depplatform; } public String getArrivalPlatform() { if(arrplatform == null) arrplatform = strings.get(readShort(offset+14)); return arrplatform; } public String getAttribute(int index) { if(attr == null) attr = attributes.get(attributesOffset); return attr[index]; } } public class Message { String start; String end; public String brief; public String full; } public class TrainChange { public PLNTimestamp realdeptime; public PLNTimestamp realarrtime; String realdepplatform,realarrplatform; } public class ConnectionChange { public int departureDelay; } public PLN(byte[] byte_data) { data = byte_data; stationsStart = readShort(0x36); attributesStart = readShort(0x3a); attributesEnd = readShort(0x3e); conCnt = readShort(0x1e); strings = new StringManager(); attributes = new AttributeManager(); messages = new MessageManager(); setupDates(); readStringTable(); readStations(); readHeaderStations(); readAvailabilities(); readConnections(); } public Station departureStation(){ return dep; } public Station arrivalStation(){ return arr; } public String id() { return strings.get(readShort(attributesEnd+0xc)); } public String ld() { try{ return strings.get(readShort(attributesEnd+0xc)+id().length()+1); } catch(Exception e) { return "hw1"; } } public int connectionCount() { if(totalConCnt == -1) { ++totalConCnt; for(UnboundConnection c : connections) totalConCnt += c.getAvailability().daysCount(); } return totalConCnt; } //Tak naprawdę, to jest to czytanie 'unsigned short' a nie 'int'. //Jednak zwracamy int, ponieważ short javowy nie zmieści wszystkich wartości //unsigned short int readShort(int pos) { int r = (int) (data[pos] & 0x000000FF); r += ((int) (data[pos+1] & 0x000000FF))*256; return r; } private void saveShort(int pos, int value) { data[pos+1] = (byte) ((value & 0x0000FF00L) >> 8); data[pos] = (byte) ((value & 0x000000FFL)); } private int readLong(int pos) { int r = (int) (data[pos] & 0x000000FF); r += ((int) (data[pos+1] & 0x000000FF))*256; r += ((int) (data[pos+2] & 0x000000FF))*65536; r += ((int) (data[pos+3] & 0x000000FF))*16777216; return r; } private String getString(int pos) { int start = pos; while(data[pos] != 0) pos++; byte[] b = new byte[pos-start]; pos = start; start = 0; while(data[pos] != 0) b[start++] = data[pos++]; try { return new String(b,"UTF-8"); } catch (UnsupportedEncodingException e) { e.printStackTrace(); } return ""; } private void setupDates() { sdate = new android.text.format.Time(); sdate.year = 1979; sdate.month = 11; sdate.monthDay = 31; edate = new android.text.format.Time(sdate); today = new android.text.format.Time(sdate); sdate.monthDay += readShort(0x28); edate.monthDay += readShort(0x2a); today.monthDay += readShort(0x2c); sdate.normalize(false); edate.normalize(false); today.normalize(false); } Train readTrain(int pos) { Train r = new Train(); r.offset = pos; r.deptime = new PLNTimestamp(readShort(pos)); r.depstation = stations[readShort(pos+2)]; r.arrtime = new PLNTimestamp(readShort(pos+4)); r.arrstation = stations[readShort(pos+6)]; r.number = strings.get(readShort(pos+10)); r.attributesOffset = readShort(pos+18); return r; } private void readStringTable() { stringStart = readShort(0x24); availabilitiesStart = readShort(0x20); } private void readHeaderStations() { dep = readHeaderStation(2); arr = readHeaderStation(0x10); } private Station readHeaderStation(int offset) { int nameLoc = readShort(offset); if(nameLoc == 0) return null; Station ret = new Station(); ret.name = strings.get(nameLoc); ret.x = readLong(offset+6); ret.y = readLong(offset+10); return ret; } private void readStations() { stations = new Station[(attributesStart-stationsStart)/StationSize]; for(int i = 0, pos = stationsStart; pos < attributesStart; pos += StationSize, i++) { stations[i] = new Station(); stations[i].name = strings.get(readShort(pos)); stations[i].id = readLong(pos+2); stations[i].x = readLong(pos+6); stations[i].y = readLong(pos+10); } } private void readAvailabilities() { availabilities = new HashMap<Integer, Availability>(); for(int i = 0, pos = availabilitiesStart; pos < stationsStart; i++, pos += 6) { int len = readShort(pos+4); int dOffset = readShort(pos+2); availabilities.put(pos-availabilitiesStart, new Availability(readShort(pos),pos+6,dOffset,len)); pos += len; } } private String[] readAttributeList(int offset) { int pos = offset + attributesStart; int cnt = readShort(pos); String[] tab = new String[cnt]; int p = pos+2; for(int i = 0; i < cnt; i++,p+=2) tab[i] = strings.get(readShort(p)); return tab; } private void readConnections() { connections = new UnboundConnection[conCnt]; int chinfo = readShort(attributesEnd+0xe); boolean hasChanges = (chinfo != 0); if(hasChanges) chinfo += conCnt*2+4; for(int i = 0; i < conCnt; i++) { connections[i] = new UnboundConnection(this, i); if(hasChanges) { connections[i].changeOffset = chinfo; chinfo += (connections[i].trainCount+1)*8; } } } private TrainChange readTrainChanges(int offset) { int rd = readShort(offset); int ra = readShort(offset+2); int rdp = readShort(offset+4); int rap = readShort(offset+6); if(ra == 0xffff && rd == 0xffff && rdp == 0 && rap == 0) return null; delayInfo = true; TrainChange tc = new TrainChange(); tc.realdeptime = (rd == 0xffff) ? null : new PLNTimestamp(rd); tc.realarrtime = (ra == 0xffff) ? null : new PLNTimestamp(ra); tc.realdepplatform = (rdp == 0) ? null : strings.get(rdp); tc.realarrplatform = (rap == 0) ? null : strings.get(rap); return tc; } public Message readMessage(int offset) { Message r = new Message(); int t = readShort(offset+8); r.end = (t == 0) ? null : strings.get(t); t = readShort(offset+6); r.start = (t == 0) ? null : strings.get(t); r.brief = strings.get(readShort(offset+12)); r.full = strings.get(readShort(offset+14)); return r; } ConnectionChange readConnectionChanges(int offset) { int delay = readShort(offset+2); if(delay == 255) return null; ConnectionChange ch = new ConnectionChange(); ch.departureDelay = delay; delayInfo = true; return ch; } public boolean hasDelayInfo() { if(delayInfo == null) { int chinfo = readShort(attributesEnd+0xe); if(chinfo == 0) delayInfo = false; else delayInfo = hasDelay(chinfo + conCnt*2+4); } return delayInfo; } boolean hasDelay(int p) { //0,0,xff,0,xff,xff,xff,xff <- puste polaczenie //xff,xff,xff,xff,0,0,0,0 <- pusty pociag for(int i = 0; i < conCnt; ++i) { if(data[p++] != 0) return true; if(data[p++] != 0) return true; if(data[p++] != -1) return true; if(data[p++] != 0) return true; for(int k = 0; k < 4; ++k) if(data[p++] != -1) return true; for(int j = 0; j < connections[i].trainCount; ++j) { for(int k = 0; k < 4; ++k) if(data[p++] != -1) return true; for(int k = 0; k < 4; ++k) if(data[p++] != 0) return true; } } return false; } public DayUtils days() { if(dayUtils == null) dayUtils = new DayUtils(this); return dayUtils; } public void addExternalDelayInfo(HashMap<String,Integer> delays) { for(int i = 0; i < conCnt; i++) { UnboundConnection c = connections[i]; for(int j = 0; j < c.trainCount; ++j) { Train t = c.getTrain(j); if(delays.containsKey(t.number)) { delayInfo = true; int arr = t.arrtime.intValue(); int mindel = delays.get(t.number); int hrs = mindel/60; arr += hrs*100; mindel -= hrs*60; arr += mindel; PLNTimestamp real = new PLNTimestamp(arr); real.normalize(); saveShort(t.changeOffset+2, real.intValue()); int dep = t.deptime.intValue(); dep += hrs*100; dep += mindel; real = new PLNTimestamp(dep); real.normalize(); saveShort(t.changeOffset, real.intValue()); //Ew. opoznienie polaczenia if(j == 0) saveShort(c.changeOffset+2, mindel); } } } } }