/******************************************************************************* * Copyright (c) MOBAC developers * * This program 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 2 of the License, or * (at your option) any later version. * * This program 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 this program. If not, see <http://www.gnu.org/licenses/>. ******************************************************************************/ package mobac.tools; import static mobac.tools.Cities.BERLIN; import java.awt.Point; import java.io.IOException; import java.io.StringWriter; import java.io.UnsupportedEncodingException; import java.net.HttpURLConnection; import java.net.URL; import java.security.MessageDigest; import java.security.SecureRandom; import java.util.ArrayList; import java.util.List; import java.util.Map; import javax.xml.bind.JAXBException; import mobac.mapsources.DefaultMapSourcesManager; import mobac.mapsources.MapSourcesManager; import mobac.mapsources.mappacks.region_oceania.NzTopoMaps; import mobac.program.Logging; import mobac.program.ProgramInfo; import mobac.program.download.TileDownLoader; import mobac.program.interfaces.HttpMapSource; import mobac.program.interfaces.MapSource; import mobac.program.interfaces.MapSpace; import mobac.program.model.EastNorthCoordinate; import mobac.program.model.Settings; import mobac.program.model.TileImageType; import mobac.utilities.Utilities; import org.apache.log4j.Logger; public class MapSourceCapabilityDetector { public static final Logger log = Logger.getLogger(MapSourceCapabilityDetector.class); public static final SecureRandom RND = new SecureRandom(); public static final EastNorthCoordinate C_DEFAULT = BERLIN; /** * @param args */ public static void main(String[] args) { // *************************************************************************** Class<? extends HttpMapSource> mapSourceClass = NzTopoMaps.class; // *************************************************************************** Logging.configureLogging(); ProgramInfo.initialize(); try { Settings.load(); } catch (JAXBException e) { System.err.println(e.getMessage()); } DefaultMapSourcesManager.initialize(); MapSourcesManager.getInstance().getAllMapSources(); List<MapSourceCapabilityDetector> result = testMapSource(mapSourceClass); MapSourceCapabilityGUI gui = new MapSourceCapabilityGUI(result); gui.setVisible(true); } public static List<MapSourceCapabilityDetector> testMapSource(Class<? extends HttpMapSource> mapSourceClass) { return testMapSource(mapSourceClass, Cities.getTestCoordinate(mapSourceClass, C_DEFAULT)); } public static List<MapSourceCapabilityDetector> testMapSource(Class<? extends HttpMapSource> mapSourceClass, EastNorthCoordinate coordinate) { if (coordinate == null) throw new NullPointerException("Coordinate not set for " + mapSourceClass.getSimpleName()); try { return testMapSource(mapSourceClass.newInstance(), coordinate); } catch (Exception e) { System.err.println("Error while testing map source: " + e.getMessage()); return null; } } public static List<MapSourceCapabilityDetector> testMapSource(String mapSourceName, EastNorthCoordinate coordinate) { MapSource mapSource = MapSourcesManager.getInstance().getSourceByName(mapSourceName); if (!(mapSource instanceof HttpMapSource)) throw new RuntimeException("Not an HTTP map source: " + mapSource.getName()); return testMapSource((HttpMapSource) mapSource, coordinate); } public static List<MapSourceCapabilityDetector> testMapSource(HttpMapSource mapSource, EastNorthCoordinate coordinate) { if (!(mapSource instanceof HttpMapSource)) throw new RuntimeException("Not an HTTP map source: " + mapSource.getName()); ArrayList<MapSourceCapabilityDetector> result = new ArrayList<MapSourceCapabilityDetector>(); for (int zoom = mapSource.getMinZoom(); zoom < mapSource.getMaxZoom(); zoom++) { MapSourceCapabilityDetector mstd = new MapSourceCapabilityDetector((HttpMapSource) mapSource, coordinate, zoom); mstd.testMapSource(); result.add(mstd); // log.trace(mstd); } return result; } private final HttpMapSource mapSource; private final EastNorthCoordinate coordinate; private final int zoom; private URL url; private HttpURLConnection c; private boolean eTagPresent = false; private boolean expirationTimePresent = false; private boolean lastModifiedTimePresent = false; private boolean ifNoneMatchSupported = false; private boolean ifModifiedSinceSupported = false; private String contentType = "?"; public MapSourceCapabilityDetector(Class<? extends HttpMapSource> mapSourceClass, EastNorthCoordinate coordinate, int zoom) throws InstantiationException, IllegalAccessException { this(mapSourceClass.newInstance(), coordinate, zoom); } public MapSourceCapabilityDetector(HttpMapSource mapSource, EastNorthCoordinate coordinate, int zoom) { this.mapSource = mapSource; if (mapSource == null) throw new NullPointerException("MapSource not set"); this.coordinate = coordinate; this.zoom = zoom; } public void testMapSource() { try { log.debug("Testing " + mapSource.toString()); MapSpace mapSpace = mapSource.getMapSpace(); //int tilex = mapSpace.cLonToX(coordinate.lon, zoom) / mapSpace.getTileSize(); //int tiley = mapSpace.cLatToY(coordinate.lat, zoom) / mapSpace.getTileSize(); Point p = mapSpace.cLonLatToXY(coordinate.lon, coordinate.lat, zoom); int tilex = p.x / mapSpace.getTileSize(); int tiley = p.y / mapSpace.getTileSize(); c = mapSource.getTileUrlConnection(zoom, tilex, tiley); url = c.getURL(); log.trace("Sample url: " + c.getURL()); log.trace("Connecting..."); c.setReadTimeout(3000); c.setRequestProperty("User-agent", ProgramInfo.getUserAgent()); c.setRequestProperty("Accept", TileDownLoader.ACCEPT); c.connect(); log.debug("Connection established - response HTTP " + c.getResponseCode()); if (c.getResponseCode() != 200) return; // printHeaders(); byte[] content = Utilities.getInputBytes(c.getInputStream()); TileImageType detectedContentType = Utilities.getImageType(content); contentType = c.getContentType(); contentType = contentType.substring(6); if ("png".equals(contentType)) contentType = "png"; else if ("jpeg".equals(contentType)) contentType = "jpg"; else contentType = "unknown: " + c.getContentType(); if (contentType.equals(detectedContentType.getFileExt())) contentType += " (verified)"; else contentType += " (unverified)"; log.debug("Image format : " + contentType); String eTag = c.getHeaderField("ETag"); eTagPresent = (eTag != null); if (eTagPresent) { // log.debug("eTag : " + eTag); testIfNoneMatch(content); } // else log.debug("eTag : -"); // long date = c.getDate(); // if (date == 0) // log.debug("Date time : -"); // else // log.debug("Date time : " + new Date(date)); long exp = c.getExpiration(); expirationTimePresent = (c.getHeaderField("expires") != null) && (exp != 0); if (exp == 0) { // log.debug("Expiration time : -"); } else { // long diff = (exp - System.currentTimeMillis()) / 1000; // log.debug("Expiration time : " + new Date(exp) // + " => " // + Utilities.formatDurationSeconds(diff)); } long modified = c.getLastModified(); lastModifiedTimePresent = (c.getHeaderField("last-modified") != null) && (modified != 0); // if (modified == 0) // log.debug("Last modified time : not set"); // else // log.debug("Last modified time : " + new // Date(modified)); testIfModified(); } catch (Exception e) { e.printStackTrace(); } log.debug("\n"); } private void testIfNoneMatch(byte[] content) throws Exception { String eTag = c.getHeaderField("ETag"); MessageDigest md5 = MessageDigest.getInstance("MD5"); byte[] digest = md5.digest(content); String hexDigest = getHexString(digest); // log.debug("content MD5 : " + hexDigest); if (hexDigest.equals(eTag)) log.debug("eTag content : md5 hex string"); String quotedHexDigest = "\"" + hexDigest + "\""; if (quotedHexDigest.equals(eTag)) log.debug("eTag content : quoted md5 hex string"); HttpURLConnection c2 = (HttpURLConnection) url.openConnection(); c2.addRequestProperty("If-None-Match", eTag); c2.connect(); int code = c2.getResponseCode(); boolean supported = (code == 304); ifNoneMatchSupported = supported; // System.out.print("If-None-Match response: "); // log.debug(b2s(supported) + " - " + code + " (" + // c2.getResponseMessage() + ")"); c2.disconnect(); } private void testIfModified() throws IOException { HttpURLConnection c2 = (HttpURLConnection) url.openConnection(); c2.setIfModifiedSince(System.currentTimeMillis() + 1000); // future date c2.connect(); int code = c2.getResponseCode(); boolean supported = (code == 304); ifModifiedSinceSupported = supported; // System.out.print("If-Modified-Since : "); // log.debug(b2s(supported) + " - " + code + " (" + // c2.getResponseMessage() + ")"); } protected void printHeaders() { log.trace("\nHeaders:"); for (Map.Entry<String, List<String>> entry : c.getHeaderFields().entrySet()) { String key = entry.getKey(); for (String elem : entry.getValue()) { if (key != null) log.debug(key + " = "); log.debug(elem); } } } @Override public String toString() { StringWriter sw = new StringWriter(); sw.append("Mapsource.........: " + mapSource.getName() + "\n"); sw.append("Current TileUpdate: " + mapSource.getTileUpdate() + "\n"); sw.append("If-None-Match.....: " + b2s(ifNoneMatchSupported) + "\n"); sw.append("ETag..............: " + b2s(eTagPresent) + "\n"); sw.append("If-Modified-Since.: " + b2s(ifModifiedSinceSupported) + "\n"); sw.append("LastModified......: " + b2s(lastModifiedTimePresent) + "\n"); sw.append("Expires...........: " + b2s(expirationTimePresent) + "\n"); return sw.toString(); } public int getZoom() { return zoom; } public boolean iseTagPresent() { return eTagPresent; } public boolean isExpirationTimePresent() { return expirationTimePresent; } public boolean isLastModifiedTimePresent() { return lastModifiedTimePresent; } public boolean isIfModifiedSinceSupported() { return ifModifiedSinceSupported; } public boolean isIfNoneMatchSupported() { return ifNoneMatchSupported; } public String getContentType() { return contentType; } private static String b2s(boolean b) { if (b) return "supported"; else return "-"; } static final byte[] HEX_CHAR_TABLE = { (byte) '0', (byte) '1', (byte) '2', (byte) '3', (byte) '4', (byte) '5', (byte) '6', (byte) '7', (byte) '8', (byte) '9', (byte) 'a', (byte) 'b', (byte) 'c', (byte) 'd', (byte) 'e', (byte) 'f' }; public static String getHexString(byte[] raw) throws UnsupportedEncodingException { byte[] hex = new byte[2 * raw.length]; int index = 0; for (byte b : raw) { int v = b & 0xFF; hex[index++] = HEX_CHAR_TABLE[v >>> 4]; hex[index++] = HEX_CHAR_TABLE[v & 0xF]; } return new String(hex, "ASCII"); } }