Subversion Repositories ES

Compare Revisions

Last modification

Ignore whitespace Rev 15 → Rev 16

/trunk/src/de/pointedears/converter/app/CurrenciesActivity.java
5,6 → 5,7
 
import android.app.Activity;
import android.os.Bundle;
import android.os.Handler;
import android.text.Editable;
import android.view.KeyEvent;
import android.view.Menu;
23,7 → 24,8
import android.widget.TextView;
import de.pointedears.converter.R;
import de.pointedears.converter.db.CurrenciesDatabase;
import de.pointedears.converter.helpers.CurrenciesUpdateThread;
import de.pointedears.converter.helpers.ConverterThread;
import de.pointedears.converter.net.RatesUpdater;
 
/**
* Activity that implements currency conversion
58,8 → 60,12
private Spinner spinnerUnit2;
private CurrenciesDatabase db;
 
private HashMap<String, HashMap<String, Double>> conversionRates;
private HashMap<String, Double> conversionRates;
private ConverterThread updateThread;
private Handler handler;
 
private RatesUpdater updateRates;
 
/** Called when the activity is first created. */
 
@Override
186,6 → 192,13
editValue2.setText("");
}
});
 
if (this.handler == null)
{
this.handler = new Handler();
}
 
this.updateThread = null;
}
 
/**
196,27 → 209,19
TableLayout tableRates =
(TableLayout) this.findViewById(R.id.currencies_table_rates);
 
for (String key : this.conversionRates.keySet())
for (Entry<String, Double> factorEntry : this.conversionRates.entrySet())
{
for (Entry<String, Double> factorEntry : this.conversionRates.get(key)
.entrySet())
{
TableRow row = new TableRow(this);
TableRow row = new TableRow(this);
 
TextView columnCurrency1 = new TextView(this);
columnCurrency1.setText(key);
row.addView(columnCurrency1);
TextView columnCurrency1 = new TextView(this);
columnCurrency1.setText(factorEntry.getKey());
row.addView(columnCurrency1);
 
TextView columnCurrency2 = new TextView(this);
columnCurrency2.setText(factorEntry.getKey());
row.addView(columnCurrency2);
TextView columnRate = new TextView(this);
columnRate.setText(factorEntry.getValue().toString());
row.addView(columnRate);
 
TextView columnRate = new TextView(this);
columnRate.setText(factorEntry.getValue().toString());
row.addView(columnRate);
 
tableRates.addView(row);
}
tableRates.addView(row);
}
}
 
242,17 → 247,34
 
Double newValue = value;
 
HashMap<String, Double> mapForCurrency =
this.conversionRates.get(selectedItemValue1);
if (mapForCurrency != null)
/*
* NOTE: Had to do it the complicated way because somehow the Android SDK
* won't get it another way
*/
Double factorToEuro = null;
if (selectedItemValue1 != null)
{
Double conversionFactor = mapForCurrency.get(selectedItemValue2);
if (conversionFactor != null)
{
newValue *= conversionFactor;
}
factorToEuro = this.conversionRates.get(selectedItemValue1);
}
 
if (factorToEuro == null)
{
factorToEuro = 1.0;
}
 
Double factorFromEuro = null;
if (selectedItemValue2 != null)
{
factorFromEuro = this.conversionRates.get(selectedItemValue2);
}
 
if (factorFromEuro == null)
{
factorFromEuro = 1.0;
}
 
newValue = newValue / factorToEuro * factorFromEuro;
 
return newValue.toString();
}
 
286,10 → 308,45
switch (item.getItemId())
{
case R.id.item_options_update:
Thread updateThread = new CurrenciesUpdateThread();
updateThread.start();
if (this.updateThread == null)
{
this.updateRates = new RatesUpdater(this);
this.updateThread =
new ConverterThread(this.updateRates, this.handler);
this.updateRates.setUpdateThread(this.updateThread);
}
 
try
{
this.updateThread.start();
// this.editValue1.setText("Gestartet!");
}
catch (IllegalThreadStateException e)
{
// this.editValue1.setText("Bereits gestartet!");
}
return true;
 
case R.id.item_options_quit:
if (this.updateThread != null)
{
try
{
this.updateThread.join();
}
catch (InterruptedException e)
{
// TODO Auto-generated catch block
}
 
// this.editValue1.setText("Gestoppt -> Warten auf Start");
}
else
{
// this.editValue1.setText("Bereits gestoppt -> Warten auf Start");
}
return true;
 
default:
return super.onOptionsItemSelected(item);
}
/trunk/src/de/pointedears/converter/helpers/CurrenciesUpdateThread.java
File deleted
Property changes:
Deleted: svn:mime-type
## -1 +0,0 ##
-text/plain
\ No newline at end of property
Index: de/pointedears/converter/helpers/ConverterThread.java
===================================================================
--- de/pointedears/converter/helpers/ConverterThread.java (nonexistent)
+++ de/pointedears/converter/helpers/ConverterThread.java (revision 16)
@@ -0,0 +1,30 @@
+package de.pointedears.converter.helpers;
+
+import android.os.Handler;
+
+/**
+ * General thread to perform background tasks
+ *
+ * @author pelinux
+ */
+public class ConverterThread extends Thread
+{
+ private Handler handler = null;
+ private Runnable runnable = null;
+
+ /**
+ * @param runnable
+ * @param handler
+ */
+ public ConverterThread(Runnable runnable, Handler handler)
+ {
+ this.handler = handler;
+ this.runnable = runnable;
+ }
+
+ @Override
+ public void run()
+ {
+ this.handler.post(this.runnable);
+ }
+}
/de/pointedears/converter/helpers/ConverterThread.java
Property changes:
Added: svn:mime-type
## -0,0 +1 ##
+text/plain
\ No newline at end of property
Index: de/pointedears/converter/helpers/ConverterNamespaceContext.java
===================================================================
--- de/pointedears/converter/helpers/ConverterNamespaceContext.java (nonexistent)
+++ de/pointedears/converter/helpers/ConverterNamespaceContext.java (revision 16)
@@ -0,0 +1,58 @@
+package de.pointedears.converter.helpers;
+
+import java.util.HashMap;
+import java.util.Iterator;
+
+import javax.xml.XMLConstants;
+import javax.xml.namespace.NamespaceContext;
+
+/**
+ * @author pelinux
+ *
+ */
+public final class ConverterNamespaceContext implements NamespaceContext
+{
+ private final HashMap<String, String> namespaces =
+ new HashMap<String, String>();
+
+ public void add(String prefix, String uri)
+ {
+ this.namespaces.put(prefix, uri);
+ }
+
+ @Override
+ public Iterator getPrefixes(String namespaceURI)
+ {
+ throw new UnsupportedOperationException();
+ }
+
+ @Override
+ public String getPrefix(String namespaceURI)
+ {
+ throw new UnsupportedOperationException();
+ }
+
+ @Override
+ public String getNamespaceURI(String prefix)
+ {
+ if (prefix == null)
+ {
+ throw new NullPointerException("Null prefix");
+ }
+ else
+ {
+ if ("xml".equals(prefix))
+ {
+ return XMLConstants.XML_NS_URI;
+ }
+
+ String storedPrefix = this.namespaces.get(prefix);
+ if (storedPrefix != null)
+ {
+ return storedPrefix;
+ }
+
+ return XMLConstants.NULL_NS_URI;
+ }
+ }
+}
\ No newline at end of file
/de/pointedears/converter/helpers/ConverterNamespaceContext.java
Property changes:
Added: svn:mime-type
## -0,0 +1 ##
+text/plain
\ No newline at end of property
Index: de/pointedears/converter/db/CurrenciesDatabase.java
===================================================================
--- de/pointedears/converter/db/CurrenciesDatabase.java (revision 15)
+++ de/pointedears/converter/db/CurrenciesDatabase.java (revision 16)
@@ -20,36 +20,22 @@
public class CurrenciesDatabase extends SQLiteOpenHelper
{
private static final String DATABASE_NAME = "currency.db"; //$NON-NLS-1$
- private static final int DATABASE_VERSION = 2;
+ private static final int DATABASE_VERSION = 3;
private static final String TABLE = "currency"; //$NON-NLS-1$
- private static final String COLUMN_CURRENCY1 = "currency1"; //$NON-NLS-1$
- private static final String COLUMN_CURRENCY2 = "currency2"; //$NON-NLS-1$
+ private static final String COLUMN_CURRENCY = "currency1"; //$NON-NLS-1$
private static final String COLUMN_FACTOR = "factor"; //$NON-NLS-1$
- private static HashMap<String, HashMap<String, Double>> conversionRates =
- new HashMap<String, HashMap<String, Double>>();
+ private static HashMap<String, Double> conversionRates =
+ new HashMap<String, Double>();
static
{
- HashMap<String, Double> conversionFactors = new HashMap<String, Double>();
- conversionFactors.put(CurrenciesActivity.VALUE_EUR, 0.767842293);
- conversionFactors.put(CurrenciesActivity.VALUE_USD, 1.03413);
- CurrenciesDatabase.conversionRates.put(CurrenciesActivity.VALUE_CHF,
- conversionFactors);
-
- conversionFactors = new HashMap<String, Double>();
- conversionFactors.put(CurrenciesActivity.VALUE_CHF, 1.30235077);
- conversionFactors.put(CurrenciesActivity.VALUE_USD, 1.3468);
- CurrenciesDatabase.conversionRates.put(CurrenciesActivity.VALUE_EUR,
- conversionFactors);
-
- conversionFactors = new HashMap<String, Double>();
- conversionFactors.put(CurrenciesActivity.VALUE_CHF, 0.966996412);
- conversionFactors.put(CurrenciesActivity.VALUE_EUR, 0.742500743);
- CurrenciesDatabase.conversionRates.put(CurrenciesActivity.VALUE_USD,
- conversionFactors);
+ /* Default conversion rates from Euro (EUR) to other currencies */
+ CurrenciesDatabase.conversionRates
+ .put(CurrenciesActivity.VALUE_CHF, 1.3013);
+ CurrenciesDatabase.conversionRates
+ .put(CurrenciesActivity.VALUE_USD, 1.3521);
}
- private final CurrenciesActivity context;
/**
* @param context
@@ -59,7 +45,6 @@
{
super(context, CurrenciesDatabase.DATABASE_NAME, null,
CurrenciesDatabase.DATABASE_VERSION);
- this.context = context;
this.readConversionsFromDatabase();
}
@@ -75,28 +60,21 @@
public void onCreate(SQLiteDatabase db)
{
db.execSQL("CREATE TABLE IF NOT EXISTS " + CurrenciesDatabase.TABLE
- + " (" + CurrenciesDatabase.COLUMN_CURRENCY1 + " TEXT, "
- + CurrenciesDatabase.COLUMN_CURRENCY2 + " TEXT, "
+ + " (" + CurrenciesDatabase.COLUMN_CURRENCY + " TEXT, "
+ CurrenciesDatabase.COLUMN_FACTOR
+ " NUMERIC"
+ ", CONSTRAINT unique_currency_pair UNIQUE ("
- + CurrenciesDatabase.COLUMN_CURRENCY1 + ", "
- + CurrenciesDatabase.COLUMN_CURRENCY2 + "))");
+ + CurrenciesDatabase.COLUMN_CURRENCY + "))");
- HashMap<String, HashMap<String, Double>> currencyConversions =
+ HashMap<String, Double> currencyConversions =
this.getConversionRates();
- for (String key : currencyConversions.keySet())
+ for (Entry<String, Double> factorEntry : currencyConversions.entrySet())
{
- for (Entry<String, Double> factorEntry : currencyConversions.get(key)
- .entrySet())
- {
- ContentValues values = new ContentValues();
- values.put(CurrenciesDatabase.COLUMN_CURRENCY1, key);
- values.put(CurrenciesDatabase.COLUMN_CURRENCY2, factorEntry.getKey());
- values.put(CurrenciesDatabase.COLUMN_FACTOR, factorEntry.getValue());
- db.insert(CurrenciesDatabase.TABLE, CurrenciesDatabase.COLUMN_FACTOR,
- values);
- }
+ ContentValues values = new ContentValues();
+ values.put(CurrenciesDatabase.COLUMN_CURRENCY, factorEntry.getKey());
+ values.put(CurrenciesDatabase.COLUMN_FACTOR, factorEntry.getValue());
+ db.insert(CurrenciesDatabase.TABLE, CurrenciesDatabase.COLUMN_FACTOR,
+ values);
}
}
@@ -119,7 +97,7 @@
/**
* @return
*/
- public HashMap<String, HashMap<String, Double>> getConversionRates()
+ public HashMap<String, Double> getConversionRates()
{
return CurrenciesDatabase.conversionRates;
}
@@ -135,11 +113,9 @@
/* Get database connection, but upgrade database first if necessary! */
SQLiteDatabase dbConn = this.getWritableDatabase();
- @SuppressWarnings("nls")
Cursor cursor =
dbConn.query(true, CurrenciesDatabase.TABLE, null, null, null, null,
- null, CurrenciesDatabase.COLUMN_CURRENCY1 + ","
- + CurrenciesDatabase.COLUMN_CURRENCY2, null);
+ null, CurrenciesDatabase.COLUMN_CURRENCY, null);
if (cursor != null)
{
@@ -147,10 +123,7 @@
{
int currency1Id =
cursor
- .getColumnIndexOrThrow(CurrenciesDatabase.COLUMN_CURRENCY1);
- int currency2Id =
- cursor
- .getColumnIndexOrThrow(CurrenciesDatabase.COLUMN_CURRENCY2);
+ .getColumnIndexOrThrow(CurrenciesDatabase.COLUMN_CURRENCY);
int factorId =
cursor.getColumnIndexOrThrow(CurrenciesDatabase.COLUMN_FACTOR);
@@ -157,58 +130,17 @@
/* NOTE: Don't change the default values if the table is empty */
if (cursor.moveToFirst())
{
- HashMap<String, HashMap<String, Double>> newCurrencyConversions =
- new HashMap<String, HashMap<String, Double>>();
- HashMap<String, Double> mapForCurrency = null;
- String lastCurrency1Str = null;
- String currency1Str;
+ HashMap<String, Double> newCurrencyConversions =
+ new HashMap<String, Double>();
do
{
- currency1Str = cursor.getString(currency1Id);
- String currency2Str = cursor.getString(currency2Id);
+ String currencyStr = cursor.getString(currency1Id);
Double factor = cursor.getDouble(factorId);
-
- if (lastCurrency1Str == null
- || !lastCurrency1Str.equals(currency1Str))
- {
- /*
- * NOTE: Update outer map when we see a new currency;
- * ORDER BY ensures we don't see a currency1 twice except
- * consecutively
- */
- if (mapForCurrency != null)
- {
- newCurrencyConversions.put(lastCurrency1Str, mapForCurrency);
- }
-
- lastCurrency1Str = new String(currency1Str);
-
- /* NOTE: New currency1: Reset inner map */
- mapForCurrency = newCurrencyConversions.get(currency1Str);
- }
-
- /* If we did not see this currency1 before */
- if (mapForCurrency == null)
- {
- mapForCurrency = new HashMap<String, Double>();
- }
-
- /*
- * NOTE: Update inner map after each table row; assignment to
- * mapForCurrency above ensures we are putting the factor
- * into the correct map.
- */
- mapForCurrency.put(currency2Str, factor);
+ newCurrencyConversions.put(currencyStr, factor);
}
while (cursor.moveToNext());
- /*
- * NOTE: Update from last table row; cursor not empty, so we can
- * skip the test for null
- */
- newCurrencyConversions.put(currency1Str, mapForCurrency);
-
CurrenciesDatabase.conversionRates = newCurrencyConversions;
}
}
@@ -237,11 +169,9 @@
{
SQLiteDatabase dbConn = this.getReadableDatabase();
- @SuppressWarnings("nls")
Cursor myCursor =
dbConn.query(true, CurrenciesDatabase.TABLE, null, null, null, null,
- null, CurrenciesDatabase.COLUMN_CURRENCY1 + ","
- + CurrenciesDatabase.COLUMN_CURRENCY2, null);
+ null, CurrenciesDatabase.COLUMN_CURRENCY, null);
@SuppressWarnings({ "unused", "nls" })
String queryResult = "";
@@ -251,10 +181,7 @@
{
int currency1Id =
myCursor
- .getColumnIndexOrThrow(CurrenciesDatabase.COLUMN_CURRENCY1);
- int currency2Id =
- myCursor
- .getColumnIndexOrThrow(CurrenciesDatabase.COLUMN_CURRENCY2);
+ .getColumnIndexOrThrow(CurrenciesDatabase.COLUMN_CURRENCY);
int factorId =
myCursor.getColumnIndexOrThrow(CurrenciesDatabase.COLUMN_FACTOR);
@@ -262,13 +189,12 @@
{
do
{
- String currency1Str = myCursor.getString(currency1Id);
- String currency2Str = myCursor.getString(currency2Id);
+ String currencyStr = myCursor.getString(currency1Id);
Double factor = myCursor.getDouble(factorId);
/* DEBUG */
queryResult +=
- currency1Str + " --> " + currency2Str + ": " + factor + "\n";
+ "EUR --> " + currencyStr + ": " + factor + "\n"; //$NON-NLS-1$ //$NON-NLS-2$ //$NON-NLS-3$
}
while (myCursor.moveToNext());
}
/trunk/src/de/pointedears/converter/net/RatesUpdater.java
0,0 → 1,179
/**
*
*/
package de.pointedears.converter.net;
 
import java.io.IOException;
 
import javax.xml.parsers.DocumentBuilder;
import javax.xml.parsers.DocumentBuilderFactory;
import javax.xml.parsers.ParserConfigurationException;
import javax.xml.xpath.XPath;
import javax.xml.xpath.XPathConstants;
import javax.xml.xpath.XPathExpression;
import javax.xml.xpath.XPathExpressionException;
import javax.xml.xpath.XPathFactory;
 
import org.w3c.dom.Document;
import org.w3c.dom.NamedNodeMap;
import org.w3c.dom.Node;
import org.w3c.dom.NodeList;
import org.xml.sax.SAXException;
 
import android.app.Notification;
import android.app.NotificationManager;
import android.app.PendingIntent;
import android.content.Context;
import android.content.Intent;
import de.pointedears.converter.R;
import de.pointedears.converter.helpers.ConverterThread;
 
/**
* @author pelinux
*
*/
public class RatesUpdater implements Runnable
{
private static final String URL_ECB =
"http://www.ecb.europa.eu/stats/eurofxref/eurofxref-daily.xml"; //$NON-NLS-1$
 
private final Context activityContext;
private ConverterThread updateThread = null;
 
/**
*
*/
public RatesUpdater(Context activityContext)
{
this.activityContext = activityContext;
}
 
/**
* @return the updateThread
*/
public ConverterThread getUpdateThread()
{
return this.updateThread;
}
 
/**
* @param updateThread
*/
public void setUpdateThread(ConverterThread updateThread)
{
this.updateThread = updateThread;
}
 
/*
* (non-Javadoc)
*
* @see java.lang.Runnable#run()
*/
@Override
public void run()
{
if (this.getUpdateThread() != null)
{
// CurrenciesActivity.this.editValue1.setText("42");
 
DocumentBuilderFactory documentBuilderFactory =
DocumentBuilderFactory.newInstance();
documentBuilderFactory.setNamespaceAware(true);
try
{
DocumentBuilder builder =
documentBuilderFactory.newDocumentBuilder();
try
{
Document doc = builder.parse(RatesUpdater.URL_ECB);
XPathFactory xpathFactory = XPathFactory.newInstance();
XPath xpath = xpathFactory.newXPath();
// NamespaceContextHelper namespaceContext =
// new NamespaceContextHelper();
// namespaceContext.add("gesmes",
// "http://www.gesmes.org/xml/2002-08-01");
// xpath.setNamespaceContext(namespaceContext);
 
try
{
/*
* FIXME: Why doesn't a simple "./Cube/Cube/Cube" work even with a
* namespace resolver?
*/
XPathExpression expr =
xpath
.compile("./*[local-name() = 'Cube']/*[local-name() = 'Cube']/*[local-name() = 'Cube']"); //$NON-NLS-1$
Object result =
expr.evaluate(doc.getDocumentElement(), XPathConstants.NODESET);
NodeList nodes = (NodeList) result;
 
int len = nodes.getLength();
 
String ns = Context.NOTIFICATION_SERVICE;
NotificationManager mNotificationManager =
(NotificationManager) this.activityContext
.getSystemService(ns);
 
int icon = R.drawable.icon;
CharSequence tickerText = "Found " + len + " nodes!";
long when = System.currentTimeMillis();
 
Notification notification =
new Notification(icon, tickerText, when);
 
Context applicationContext =
this.activityContext.getApplicationContext();
CharSequence contentTitle = "Converter";
CharSequence contentText = "Found " + len + " nodes!";
Intent notificationIntent =
new Intent(this.activityContext, this.activityContext.getClass());
PendingIntent contentIntent =
PendingIntent.getActivity(this.activityContext, 0,
notificationIntent, 0);
 
notification.setLatestEventInfo(applicationContext, contentTitle,
contentText,
contentIntent);
 
// private static final int HELLO_ID = 1;
 
mNotificationManager.notify(1, notification);
 
for (int i = 0; i < len; ++i)
{
Node item = nodes.item(i);
NamedNodeMap attributes = item.getAttributes();
String currency =
attributes
.getNamedItem("currency").getNodeValue(); //$NON-NLS-1$
String rate = attributes.getNamedItem("rate").getNodeValue(); //$NON-NLS-1$
 
/* TODO: Update UI */
System.out.println(currency + ": " + rate); //$NON-NLS-1$
}
}
catch (XPathExpressionException e)
{
// TODO Auto-generated catch block
e.printStackTrace();
}
}
catch (SAXException e)
{
// TODO Auto-generated catch block
e.printStackTrace();
}
catch (IOException e)
{
// TODO Auto-generated catch block
e.printStackTrace();
}
}
catch (ParserConfigurationException e)
{
// TODO Auto-generated catch block
e.printStackTrace();
}
}
}
}
Property changes:
Added: svn:mime-type
## -0,0 +1 ##
+text/plain
\ No newline at end of property
Index: com/nwalsh/namespace/NamespaceContextHelper.java
===================================================================
--- com/nwalsh/namespace/NamespaceContextHelper.java (nonexistent)
+++ com/nwalsh/namespace/NamespaceContextHelper.java (revision 16)
@@ -0,0 +1,394 @@
+/*
+ * The contents of this file are subject to the terms
+ * of the Common Development and Distribution License
+ * (the "License"). You may not use this file except
+ * in compliance with the License.
+ *
+ * You can obtain a copy of the license at
+ * https://jaxp.dev.java.net/CDDLv1.0.html.
+ * See the License for the specific language governing
+ * permissions and limitations under the License.
+ *
+ * When distributing Covered Code, include this CDDL
+ * HEADER in each file and include the License file at
+ * https://jaxp.dev.java.net/CDDLv1.0.html
+ * If applicable add the following below this CDDL HEADER
+ * with the fields enclosed by brackets "[]" replaced with
+ * your own identifying information: Portions Copyright
+ * [year] [name of copyright owner]
+ */
+
+/*
+ * $Id: NamespaceContextHelper.java,v 1.2 2006-03-28 20:54:02 ndw Exp $
+ * Copyright 2006 Sun Microsystems, Inc. All Rights Reserved.
+ * Portions Copyright 2010 Thomas 'PointedEars' Lahn
+ */
+
+package com.nwalsh.namespace;
+
+import java.util.Enumeration;
+import java.util.Hashtable;
+import java.util.Iterator;
+
+import javax.xml.namespace.NamespaceContext;
+
+/*
+ * NOTE: Skip XML 1.1 validity tests because we don't want to recompile
+ * org.apache.xerces for Android -- PointedEars
+ */
+// import org.apache.xerces.util.XML11Char;
+
+/**
+ * Helper implementation of the javax.xml.namespace.NamespaceContext interface.
+ *
+ * <p>
+ * This class implements the JAXP (1.3+)
+ * {@link javax.xml.namespace.NamespaceContext} interface. This is the interface
+ * used by the JAXP XPath APIs to establish namespace bindings for XPath
+ * expressions.
+ * <p>
+ *
+ * <p>
+ * There are two errors (in retrospect) with respect to the namespace context in
+ * the XPath API. First, there's no way to construct a new one. Given an XPath
+ * you can find out what context it is using, but if you want to construct a new
+ * expression, there's no standard class that you can instantiate to build a new
+ * context. Second, the {@link javax.xml.namespace.NamespaceContext} interface
+ * is obviously (again, in retrospect) missing a method that returns an iterator
+ * that will allow you to find <emph>all</emph> the namespace URIs (and/or
+ * prefixes, which would be equivalent) in the context.
+ * </p>
+ *
+ * <p>
+ * This class addresses the first error by providing an object that you can
+ * instantiate that implements the {@link javax.xml.namespace.NamespaceContext}
+ * interface. It's not a <emph>standard</emph> class, but it at least saves you
+ * the trouble of writing it yourself. (Feel free to move it into your own
+ * package, of course.)
+ * </p>
+ *
+ * <p>
+ * There's really no way to address the second error. An interface, like
+ * {@link javax.xml.namespace.NamespaceContext}, is immutable once released into
+ * the wild in the Java platform. (This is a consequence of backwards
+ * compatibility rules.) To really address the problem, we'll have to invent a
+ * new interface or provide an alternative abstract class that implementations
+ * will be required to use, or something. However, as an experiment, this class
+ * implements a couple of extra methods that we might wish had been in the
+ * interface. These methods are carefully identified as non-standard. Having
+ * them here really isn't all that useful because your underlying XPath
+ * implementation isn't likely to return instances of this class.
+ * </p>
+ *
+ * <p>
+ * There are three ways to instantiate this class:
+ * </p>
+ *
+ * <ol>
+ * <li>The no-argument constructor produces an initially empty namespace
+ * context.</li>
+ * <li>Another constructor takes a prefix and URI and produces a namespace
+ * context with that binding.</li>
+ * <li>Finally, there's a constructor that takes a hash of namespace/uri pairs
+ * and produces a namespace context with those initial bindings.</li>
+ * <li>The obvious constructor, one that takes an existing
+ * {@link javax.xml.namespace.NamespaceContext} so that you can extend it, isn't
+ * there because you can't get the current bindings from that interface; see the
+ * aforementioned bug.</li>
+ * </ol>
+ *
+ * <p>
+ * After the object has been instantiated, you can call the
+ * {@link #add(String,String)} method to add additional bindings to the
+ * namespace context. Because I'm not sure how and where the XPath API
+ * implementations might save pointers to the context object, I've imposed a
+ * number of rules designed to make sure that the context remains coherent:
+ * </p>
+ *
+ * <ul>
+ * <li>Namespace bindings can only be added, not removed.</li>
+ * <li>Once a prefix is bound, its binding cannot be changed.</li>
+ * <li>The XML restrictions on the 'xml' prefix, the 'xmlns' prefix, and their
+ * respective namespace URIs are enforced.</li>
+ * <li>Namespace prefixes must be valid NCNames (or "" for the default
+ * namespace). Note that unprefixed element and attribute names in an XPath
+ * expression can <em>never</em> match a name that's in a namespace. In
+ * particular, setting the default namespace won't have that effect.</li>
+ * </ul>
+ *
+ * <p>
+ * Even with these rules, you can't assume that the context is thread safe.
+ * Don't allow it to be changed while someone else is reading it.
+ * </p>
+ *
+ * <p>
+ * <b>Other notes:</b>
+ * </p>
+ *
+ * <ul>
+ * <li>There's no <code>getNamespaceURIs(String prefix)</code> method because
+ * there can be at most one URI bound to any given prefix. Wrapping an interator
+ * around the mapping available with {@link #getNamespaceURI(String)} seemed
+ * silly.</li>
+ * <li>This class relies on {@link org.apache.xerces.util.XML11Char} to test
+ * that the prefixes are valid NCNames. Note that this means that they're valid
+ * XML 1.1 names. XML 1.1 names are a superset of XML 1.0 names and it didn't
+ * seem worth the extra effort that would be required to allow the user to
+ * choose XML 1.0 or XML 1.1. You might not think it's worth the effort to check
+ * at all. Fair enough.</li>
+ * <li>I've used generics here and there to make the JDK 1.5 compiler stop
+ * complaining. Just delete them for JDK 1.4 and everything should work fine.</li>
+ * </ul>
+ *
+ * @author <a href="mailto:Norman.Walsh@Sun.COM">Norman Walsh</a>
+ * @version $Revision: 1.2 $, $Date: 2006-03-28 20:54:02 $
+ * @see <a href="http://jaxp.dev.java.net/">Java API for XML Processing</a>
+ * @see <a href="http://www.w3.org/TR/REC-xml-names/#ns-qualnames"> Namespaces
+ * in XML</a>
+ * @see <a href="http://www.w3.org/XML/xml-names-19990114-errata"> Namespaces in
+ * XML Errata</a>
+ */
+public class NamespaceContextHelper implements NamespaceContext
+{
+ private final Hashtable<String, String> ns = new Hashtable<String, String>();
+
+ /**
+ * Creates a new instance of NamespaceContextHelper.
+ *
+ * <p>
+ * Creates an empty namespace context.
+ * </p>
+ */
+ public NamespaceContextHelper()
+ {
+ }
+
+ /**
+ * Creates a new instance of NamespaceContextHelper.
+ *
+ * <p>
+ * Creates a namespace context with the bindings specified in
+ * <code>initialNamespaces</code>.
+ * </p>
+ */
+ public NamespaceContextHelper(Hashtable initialNamespaces)
+ {
+ Enumeration keys = initialNamespaces.keys();
+ while (keys.hasMoreElements())
+ {
+ String prefix = (String) keys.nextElement();
+ String uri = (String) initialNamespaces.get(prefix);
+
+ this.add(prefix, uri);
+ }
+ }
+
+ /**
+ * Creates a new instance of NamespaceContextHelper.
+ *
+ * <p>
+ * Creates a namespace context with the specified <code>prefix</code> bound to
+ * <code>uri</code>.
+ * </p>
+ */
+ public NamespaceContextHelper(String prefix, String uri)
+ {
+ this.add(prefix, uri);
+ }
+
+ /**
+ * Adds a new prefix/uri binding to the namespace context.
+ *
+ * @throws NullPointerException
+ * if the <code>prefix</code> or <code>uri</code> is
+ * <code>null</code>.
+ * @throws IllegalArgumentException
+ * if the caller attempts to change the binding of
+ * <code>prefix</code>, if the caller attempts to bind the prefix "
+ * <code>xml</code>"
+ * or the namespace "
+ * <code>http://www.w3.org/XML/1998/namespace</code>" incorrectly,
+ * if the caller attempts to bind the prefix "<code>xmlns</code>" or
+ * the namespace
+ * "<code>http://www.w3.org/2000/xmlns</code>", or if the
+ * <code>prefix</code> is
+ * not a valid NCName.
+ */
+ public void add(String prefix, String uri)
+ {
+ if (prefix == null || uri == null)
+ {
+ throw new NullPointerException(
+ "Null prefix or uri passed to NamespaceContextHelper");
+ }
+
+ if (this.ns.containsKey(prefix))
+ {
+ String curURI = this.ns.get(prefix);
+ if (uri.equals(curURI))
+ {
+ return;
+ }
+ throw new IllegalArgumentException(
+ "Attempt to change binding in NamespaceContextHelper");
+ }
+
+ if ("xml".equals(prefix)
+ && !"http://www.w3.org/XML/1998/namespace".equals(uri))
+ {
+ throw new IllegalArgumentException(
+ "The prefix 'xml' can only be bound to 'http://www.w3.org/XML/1998/namespace' in NamespaceContextHelper");
+ }
+
+ if ("http://www.w3.org/XML/1998/namespace".equals(uri)
+ && !"xml".equals(prefix))
+ {
+ throw new IllegalArgumentException(
+ "The namespace 'http://www.w3.org/XML/1998/namespace' can only have the prefix 'xml' in NamespaceContextHelper");
+ }
+
+ if ("xmlns".equals(prefix)
+ || "http://www.w3.org/2000/xmlns".equals(uri))
+ {
+ throw new IllegalArgumentException(
+ "Neither the prefix 'xmlns' nor the URI 'http://www.w3.org/2000/xmlns' can be bound in NamespaceContextHelper");
+ }
+
+ if ("".equals(prefix))
+ {
+ this.ns.put(prefix, uri);
+ }
+ else
+ {
+ /*
+ * NOTE: Skip XML 1.1 validity tests because we don't want to recompile
+ * org.apache.xerces for Android -- PointedEars
+ */
+ // if (XML11Char.isXML11ValidNCName (prefix)) {
+ this.ns.put(prefix, uri);
+ // } else {
+ // throw new IllegalArgumentException
+ // ("Prefix is not a valid NCName in NamespaceContextHelper");
+ // }
+ }
+ }
+
+ /** Implements the NamespaceContext getNamespaceURI method. */
+ public String getNamespaceURI(String prefix)
+ {
+ return this.ns.get(prefix);
+ }
+
+ /** Implements the NamespaceContext getPrefix method. */
+ public String getPrefix(String namespaceURI)
+ {
+ if (this.ns.containsValue(namespaceURI))
+ {
+ Enumeration<String> keys = this.ns.keys();
+ while (keys.hasMoreElements())
+ {
+ String pfx = keys.nextElement();
+ String uri = this.ns.get(pfx);
+ if (namespaceURI.equals(uri))
+ {
+ return pfx;
+ }
+ }
+ }
+ return null;
+ }
+
+ /**
+ * Implements a <emph>NON STANDARD</emph> method for finding all of the
+ * prefixes
+ * in the namespace context.
+ *
+ * <p>
+ * Returns an iterator over all of the prefixes in the namespace context. Note
+ * that multiple prefixes may be bound to the same URI.
+ * </p>
+ */
+ public Iterator getPrefixes()
+ {
+ return this.getPrefixes(null);
+ }
+
+ /** Implements the NamespaceContext getPrefixes method. */
+ public Iterator getPrefixes(String namespaceURI)
+ {
+ return new NSIterator(this.ns, namespaceURI);
+ }
+
+ /**
+ * Implements a <emph>NON STANDARD</emph> method for finding all of the
+ * namespace URIs
+ * in the namespace context.
+ *
+ * <p>
+ * Returns an iterator over all of the namespace URIs in the namespace
+ * context. Note that each namespace URI is returned exactly once, even if it
+ * is bound to several different prefixes.
+ * </p>
+ */
+ public Iterator getNamespaceURIs()
+ {
+ // Make sure each URI is returned at most once...
+ Hashtable<String, String> uriHash = new Hashtable<String, String>();
+ Enumeration<String> keys = this.ns.keys();
+ while (keys.hasMoreElements())
+ {
+ String pfx = keys.nextElement();
+ String uri = this.ns.get(pfx);
+ if (!uriHash.containsKey(uri))
+ {
+ uriHash.put(uri, pfx);
+ }
+ }
+
+ return new NSIterator(uriHash, null);
+ }
+
+ /** Implements the Iterator interface over namespace bindings. */
+ private class NSIterator implements Iterator
+ {
+ private Enumeration<String> keys;
+
+ public NSIterator(Hashtable<String, String> hash, String value)
+ {
+ this.keys = hash.keys();
+ if (value != null)
+ {
+ // We have to copy the hash to get only the keys that have the specified
+ // value
+ Hashtable<String, String> vHash = new Hashtable<String, String>();
+ while (this.keys.hasMoreElements())
+ {
+ String key = this.keys.nextElement();
+ String val = hash.get(key);
+ if (val.equals(value))
+ {
+ vHash.put(key, val);
+ }
+ }
+ this.keys = vHash.keys();
+ }
+ }
+
+ public boolean hasNext()
+ {
+ return this.keys.hasMoreElements();
+ }
+
+ public String next()
+ {
+ return this.keys.nextElement();
+ }
+
+ public void remove()
+ {
+ throw new UnsupportedOperationException(
+ "Cannot remove prefix in NamespaceContextHelper");
+ }
+ }
+
+}
/com/nwalsh/namespace/NamespaceContextHelper.java
Property changes:
Added: svn:mime-type
## -0,0 +1 ##
+text/plain
\ No newline at end of property
Index: com/nwalsh/namespace/package.html
===================================================================
--- com/nwalsh/namespace/package.html (nonexistent)
+++ com/nwalsh/namespace/package.html (revision 16)
@@ -0,0 +1,49 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<!--
+/*
+ * The contents of this file are subject to the terms
+ * of the Common Development and Distribution License
+ * (the "License"). You may not use this file except
+ * in compliance with the License.
+ *
+ * You can obtain a copy of the license at
+ * https://jaxp.dev.java.net/CDDLv1.0.html.
+ * See the License for the specific language governing
+ * permissions and limitations under the License.
+ *
+ * When distributing Covered Code, include this CDDL
+ * HEADER in each file and include the License file at
+ * https://jaxp.dev.java.net/CDDLv1.0.html
+ * If applicable add the following below this CDDL HEADER
+ * with the fields enclosed by brackets "[]" replaced with
+ * your own identifying information: Portions Copyright
+ * [year] [name of copyright owner]
+ */
+
+/*
+ * $Id: package.html,v 1.1 2006-03-28 20:54:02 ndw Exp $
+ * Copyright 2006 Sun Microsystems, Inc. All Rights Reserved.
+ */
+-->
+<!DOCTYPE html
+ PUBLIC "-//W3C//DTD XHTML 1.0 Strict//EN"
+ "http://www.w3.org/TR/xhtml1/DTD/xhtml1-strict.dtd">
+
+<html xmlns="http://www.w3.org/1999/xhtml">
+
+<head>
+ <title>com.nwalsh.namespace</title>
+
+ <meta name="CVS"
+ content="$Id: package.html,v 1.1 2006-03-28 20:54:02 ndw Exp $" />
+ <meta name="AUTHOR"
+ content="Norman.Walsh@Sun.COM" />
+</head>
+
+<body>
+
+<p>This package contains a helper class for the
+javax.xml.namespace.NamespaceContext interface and the XPath APIs.</p>
+
+</body>
+</html>
/com/nwalsh/namespace/package.html
Property changes:
Added: svn:mime-type
## -0,0 +1 ##
+text/plain
\ No newline at end of property