blob: f4a06efd2e55cce6496a66e7f9138547519df3dc [file] [log] [blame]
/*
* ******************************************************************************
* Copyright (C) 2007, International Business Machines Corporation and others.
* All Rights Reserved.
* ******************************************************************************
*/
package com.ibm.icu.dev.tool.tzu;
import java.io.BufferedReader;
import java.io.File;
import java.io.IOException;
import java.io.InputStreamReader;
import java.net.MalformedURLException;
import java.net.URL;
import java.net.URLConnection;
import java.util.Iterator;
import java.util.TreeMap;
import javax.swing.AbstractListModel;
import javax.swing.ComboBoxModel;
import javax.swing.text.MutableAttributeSet;
import javax.swing.text.html.HTML;
import javax.swing.text.html.HTMLEditorKit;
import javax.swing.text.html.parser.ParserDelegator;
/**
* Represents a map of timezone version names to urls where they can be found that is usable by any
* class that uses AbstractListModels (such as a JList or JCombobox in swing). Also contains methods
* to begin a search for sources on the ICU Timezone Repository.
*/
class SourceModel extends AbstractListModel implements ComboBoxModel {
/**
* The serializable UID.
*/
public static final long serialVersionUID = 1339;
/**
* The URL of the ICU Timezone Repository. In order to catch a MalformedURLException, this field
* must be initialized by the constructor.
*/
public static URL TZ_BASE_URL = null;
/**
* The end of a URL string to any timezone resource in the ICU Timezone Repository meant for
* ICU4J.
*/
public static final String TZ_BASE_URLSTRING_END = "/be/zoneinfo.res";
/**
* The URL string of the ICU Timezone Repository.
*/
public static final String TZ_BASE_URLSTRING_START = "http://icu-project.org/tzdata/";
/**
* The readable name of the local timezone resource file, ie. "Local Copy" or "Local Copy
* (2007c)". Since the version is determined by a <code>ICUFile.findFileTZVersion</code>,
* this field must be initialized by the constructor.
*/
public static String TZ_LOCAL_CHOICE = null;
/**
* The URL of the local timezone resource file. In order to catch a MalformedURLException, this
* field must be initialized by the constructor.
*/
public static URL TZ_LOCAL_URL = null;
/**
* The version of the local timezone resource file, ie. "2007c". Since the version is determined
* by a <code>ICUFile.findFileTZVersion</code>, this field must be initialized by the
* constructor.
*/
public static String TZ_LOCAL_VERSION = null;
/**
* The local timezone resource file.
*/
public static File tzLocalFile = null;
/**
* The current logger.
*/
private Logger logger;
/**
* The currently selected timezone resource name. Initially set to <code>TZ_LOCAL_CHOICE</code>.
*/
private Object selected = TZ_LOCAL_CHOICE;
/**
* The map of timezone resource names to their respective URL locations.
*/
private TreeMap urlMap = new TreeMap();
/**
* Constructs a source model.
*
* @param logger
* The current logger.
* @param tzLocalFile
* The local timezone resource file.
*/
public SourceModel(Logger logger, File tzLocalFile) {
this.logger = logger;
// if all constants are not yet initialized
// (this is where they get initialized)
if (TZ_BASE_URL == null) {
try {
TZ_BASE_URL = new URL(TZ_BASE_URLSTRING_START);
SourceModel.tzLocalFile = tzLocalFile;
if (!tzLocalFile.exists()) {
// not a critical error, but we won't be able to use the
// local tz file
logger.errorln("Local copy (zoneinfo.res) does not exist (perhaps you are not running icutzu from the correct directory?)");
} else {
TZ_LOCAL_URL = tzLocalFile.toURL();
TZ_LOCAL_VERSION = ICUFile.findFileTZVersion(tzLocalFile, logger);
if (TZ_LOCAL_VERSION == null) {
logger.errorln("Failed to determine version of local copy");
TZ_LOCAL_CHOICE = "Local Copy";
} else {
TZ_LOCAL_CHOICE = "Local Copy (" + TZ_LOCAL_VERSION + ")";
}
selected = TZ_LOCAL_CHOICE;
}
} catch (MalformedURLException ex) {
// this shouldn't happen
logger.errorln("Internal program error.");
ex.printStackTrace();
}
}
}
/**
* Gathers all the names and urls of timezone resources available on the ICU Timezone
* Repository. Also sets the selected item in this list to be the best timezone version
* available.
*/
public void findSources() {
BufferedReader reader = null;
try {
URLConnection con = TZ_BASE_URL.openConnection();
con.setRequestProperty("user-agent", System.getProperty("http.agent"));
reader = new BufferedReader(new InputStreamReader(con.getInputStream()));
// create an html callback function to parse through every list item
// (every list item
HTMLEditorKit.ParserCallback htmlCallback = new HTMLEditorKit.ParserCallback() {
private boolean listItem = false;
public void handleEndTag(HTML.Tag tag, int pos) {
if (tag == HTML.Tag.LI)
listItem = false;
}
public void handleStartTag(HTML.Tag tag, MutableAttributeSet attr, int pos) {
if (tag == HTML.Tag.LI)
listItem = true;
}
public void handleText(char[] data, int pos) {
if (listItem) {
String str = new String(data);
if (str.charAt(str.length() - 1) == '/')
str = str.substring(0, str.length() - 1);
if (!"..".equals(str))
try {
// add the new item to the map
urlMap.put(str, new URL(TZ_BASE_URLSTRING_START + str
+ TZ_BASE_URLSTRING_END));
// update the selected item and fire off an
// event
selected = urlMap.lastKey();
int index = 0;
for (Iterator iter = urlMap.keySet().iterator(); iter.hasNext();) {
if (iter.next().equals(str))
index++;
}
fireIntervalAdded(this, index, index);
} catch (MalformedURLException ex) {
logger.errorln("Internal program error.");
ex.printStackTrace();
}
}
}
};
new ParserDelegator().parse(reader, htmlCallback, false);
} catch (IOException ex) {
// cannot connect to the repository -- use local version only.
String message = "Failed to connect to the ICU Timezone Repository at "
+ TZ_BASE_URL.toString()
+ " .\n\n"
+ "Check your connection and re-run ICUTZU or continue using the local copy of the timezone update (version "
+ TZ_LOCAL_VERSION + ").";
logger.showInformationDialog(message);
} finally {
// close the reader gracefully
if (reader != null)
try {
reader.close();
} catch (IOException ex) {
}
}
}
/**
* Returns the name of the timezone resource at the given index.
*
* @param index
* The given index.
* @return The name of the timezone resource at the given index.
*/
public Object getElementAt(int index) {
if (index == 0)
return TZ_LOCAL_CHOICE;
else if (index < 0 || index > urlMap.size())
return null;
else {
Iterator iter = urlMap.keySet().iterator();
for (int i = 1; i < index; i++)
iter.next();
return iter.next();
}
}
/**
* Returns the selected timezone resource name. If <code>setSelectedItem</code> is never
* called externally, then this is also the best available timezone resource (the most recent
* version).
*
* @return The selected timezone resource name.
*/
public Object getSelectedItem() {
return selected;
}
/**
* Gets the number of timezone resources currently stored by the source model. There will always
* be at least one.
*
* @return The number of timezone resources.
*/
public int getSize() {
// the added size (+1) is due to the local copy not being inside the map
return urlMap.size() + 1;
}
/**
* Returns the URL mapped to by the given timezone resource name.
*
* @param choice
* The version name, which should be a String.
* @return The URL mapped to by the given timezone resource name.
*/
public URL getURL(Object choice) {
if (choice == null || !(choice instanceof String))
return null;
else if (TZ_LOCAL_CHOICE.equalsIgnoreCase((String) choice))
return TZ_LOCAL_URL;
else
return (URL) urlMap.get(choice);
}
/**
* Returns the version of a timezone resource with the given name. Usually this is exactly the
* same as the name, but if the name is <code>TZ_LOCAL_CHOICE</code> (ignoring case), then the
* version is <code>TZ_LOCAL_VERSION</code>.
*
* @param choice
* The version name, which should be a String.
* @return The URL mapped to by the given version name.
*/
public String getVersion(Object choice) {
if (choice == null || !(choice instanceof String))
return null;
else if (TZ_LOCAL_CHOICE.equalsIgnoreCase((String) choice))
return TZ_LOCAL_VERSION;
else
return (String) choice;
}
/**
* Returns an iterator on the entry set of the url map. Each item iterated will be of type
* Map.Entry, whos key is a String and value is a URL.
*
* @return An iterator as described above.
*/
public Iterator iterator() {
return urlMap.entrySet().iterator();
}
/**
* Sets the selected timezone resource name.
*
* @param selected
* The timezone resource name to be selected.
*/
public void setSelectedItem(Object selected) {
this.selected = selected;
}
}