openjpa-commits mailing list archives

Site index · List index
Message view « Date » · « Thread »
Top « Date » · « Thread »
From p..@apache.org
Subject svn commit: r406215 [8/10] - in /incubator/openjpa/trunk/openjpa-lib: ./ java/ java/org/ java/org/apache/ java/org/apache/openjpa/ java/org/apache/openjpa/lib/ java/org/apache/openjpa/lib/conf/ java/org/apache/openjpa/lib/jdbc/ java/org/apache/openjpa/...
Date Sun, 14 May 2006 03:26:02 GMT
Added: incubator/openjpa/trunk/openjpa-lib/java/org/apache/openjpa/lib/util/EfficientEmptyReferenceMap.java
URL: http://svn.apache.org/viewcvs/incubator/openjpa/trunk/openjpa-lib/java/org/apache/openjpa/lib/util/EfficientEmptyReferenceMap.java?rev=406215&view=auto
==============================================================================
--- incubator/openjpa/trunk/openjpa-lib/java/org/apache/openjpa/lib/util/EfficientEmptyReferenceMap.java (added)
+++ incubator/openjpa/trunk/openjpa-lib/java/org/apache/openjpa/lib/util/EfficientEmptyReferenceMap.java Sat May 13 20:25:56 2006
@@ -0,0 +1,130 @@
+/*
+ * Copyright 2006 The Apache Software Foundation.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *     http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package org.apache.openjpa.lib.util;
+
+
+import java.util.*;
+
+import org.apache.commons.collections.map.*;
+
+
+/**
+ *	<p>Reference map that is very efficient when it knows that it contains
+ *	no entries, such as after being cleared.</p>
+ *
+ *	@author		Abe White
+ *	@nojavadoc
+ */
+public class EfficientEmptyReferenceMap
+	extends ReferenceMap
+{
+	private boolean _empty = true;
+
+
+	/**
+	 *	Default constructor.
+	 */
+	public EfficientEmptyReferenceMap ()
+	{
+		super ();
+	}
+
+
+	/**
+	 *	Allow specification of reference type to use for keys and values.
+	 */
+	public EfficientEmptyReferenceMap (int keyRef, int valRef)
+	{
+		super (keyRef, valRef);
+	}
+
+
+	public Object put (Object key, Object val)
+	{
+		_empty = false;
+		return super.put (key, val);
+	}
+
+
+	public void putAll (Map map)
+	{
+		_empty = false;
+		super.putAll (map);
+	}
+
+
+	public Object get (Object key)
+	{
+		if (_empty)
+			return null;
+		return super.get (key);
+	}
+
+
+	public Object remove (Object key)
+	{
+		if (_empty)
+			return null;
+		return super.remove (key);
+	}
+
+
+	public Set keySet ()
+	{
+		if (_empty)
+			return Collections.EMPTY_SET;
+		return super.keySet ();
+	}
+
+
+	public Collection values ()
+	{
+		if (_empty)
+			return Collections.EMPTY_SET;
+		return super.values ();
+	}
+
+
+	public Set entrySet ()
+	{
+		if (_empty)
+			return Collections.EMPTY_SET;
+		return super.entrySet ();
+	}
+
+
+	public void clear ()
+	{
+		super.clear ();
+		_empty = true;
+	}
+
+
+	public int size ()
+	{
+		if (_empty)
+			return 0;
+		return super.size ();
+	}
+
+
+	public boolean isEmpty ()
+	{
+		if (_empty)
+			return true;
+		return super.isEmpty ();
+	}
+}

Propchange: incubator/openjpa/trunk/openjpa-lib/java/org/apache/openjpa/lib/util/EfficientEmptyReferenceMap.java
------------------------------------------------------------------------------
    svn:executable = *

Added: incubator/openjpa/trunk/openjpa-lib/java/org/apache/openjpa/lib/util/ExpirationNotifyingReferenceMap.java
URL: http://svn.apache.org/viewcvs/incubator/openjpa/trunk/openjpa-lib/java/org/apache/openjpa/lib/util/ExpirationNotifyingReferenceMap.java?rev=406215&view=auto
==============================================================================
--- incubator/openjpa/trunk/openjpa-lib/java/org/apache/openjpa/lib/util/ExpirationNotifyingReferenceMap.java (added)
+++ incubator/openjpa/trunk/openjpa-lib/java/org/apache/openjpa/lib/util/ExpirationNotifyingReferenceMap.java Sat May 13 20:25:56 2006
@@ -0,0 +1,184 @@
+/*
+ * Copyright 2006 The Apache Software Foundation.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *     http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package org.apache.openjpa.lib.util;
+
+
+import java.lang.ref.*;
+
+import org.apache.commons.collections.map.*;
+
+
+/**
+ *	<p>Reference map that provides overridable methods to take action when
+ *	a key or value expires, assuming the other member of the map entry uses
+ *	a hard reference.</p>
+ *
+ *	@author		Abe White
+ *	@since		4.0
+ *	@nojavadoc
+ */
+public class ExpirationNotifyingReferenceMap
+	extends ReferenceMap
+{
+	public ExpirationNotifyingReferenceMap ()
+	{
+	}
+
+
+	public ExpirationNotifyingReferenceMap (int keyType, int valueType)
+	{
+		super (keyType, valueType);
+	}
+
+
+	public ExpirationNotifyingReferenceMap (int keyType, int valueType,
+		int capacity, float loadFactor)
+	{
+		super (keyType, valueType, capacity, loadFactor);
+	}
+
+
+	/**
+	 *	Notification that the value for the given key has expired.
+	 */
+	protected void valueExpired (Object key)
+	{
+	}
+
+	
+	/**
+	 *	Notification that the key for the given value has expired.
+	 */
+	protected void keyExpired (Object value)
+	{
+	}
+
+
+	/**
+	 *	Remove expired references.
+	 */
+	public void removeExpired ()
+	{
+		purge ();
+	}
+
+
+	protected HashEntry createEntry (HashEntry next, int hashCode, Object key,
+		Object value)
+	{
+		return new AccessibleEntry (this, next, hashCode, key, value);
+	}
+
+
+	protected void purge (Reference ref)
+	{
+		// the logic for this method is taken from the original purge method
+		// we're overriding, with added logic to track the expired key/value
+		int index = hashIndex (ref.hashCode (), data.length);
+		AccessibleEntry entry = (AccessibleEntry) data[index];
+		AccessibleEntry prev = null;
+		Object key = null, value = null;
+		while (entry != null)
+		{
+			if (purge (entry, ref))
+			{
+				if (keyType == HARD)
+					key = entry.key ();
+				else if (valueType == HARD)
+					value = entry.value ();
+
+				if (prev == null)
+					data[index] = entry.nextEntry ();
+				else
+					prev.setNextEntry (entry.nextEntry ());
+				size--;
+				break;
+			}
+			prev = entry;
+			entry = entry.nextEntry ();
+		}
+		
+		if (key != null)
+			valueExpired (key);
+		else if (value != null)
+			keyExpired (value);
+	}
+
+
+	/**
+	 *	See the code for <code>ReferenceMap.ReferenceEntry.purge</code>.
+	 */
+	private boolean purge (AccessibleEntry entry, Reference ref)
+	{
+		boolean match = (keyType != HARD && entry.key () == ref)
+			|| (valueType != HARD && entry.value () == ref);
+		if (match)
+		{
+			if (keyType != HARD)
+				((Reference) entry.key ()).clear ();
+			if (valueType != HARD)
+				((Reference) entry.value ()).clear ();
+			else if (purgeValues)
+				entry.nullValue ();
+		}
+		return match;
+	}
+
+
+	/**
+	 *	Extension of the base entry type that allows our outer class to access
+	 *	protected state.
+	 */
+	private static class AccessibleEntry
+		extends ReferenceEntry
+	{
+		public AccessibleEntry (AbstractReferenceMap map, HashEntry next,
+			int hashCode, Object key, Object value)
+		{
+			super (map, next, hashCode, key, value);
+		}
+
+
+		public Object key ()
+		{
+			return key;
+		}
+
+
+		public Object value ()
+		{
+			return value;
+		}
+
+
+		public void nullValue ()
+		{
+			value = null;
+		}
+
+
+		public AccessibleEntry nextEntry ()
+		{
+			return (AccessibleEntry) next;
+		}
+
+
+		public void setNextEntry (AccessibleEntry next)
+		{
+			this.next = next;
+		}
+	}
+}

Propchange: incubator/openjpa/trunk/openjpa-lib/java/org/apache/openjpa/lib/util/ExpirationNotifyingReferenceMap.java
------------------------------------------------------------------------------
    svn:executable = *

Added: incubator/openjpa/trunk/openjpa-lib/java/org/apache/openjpa/lib/util/Files.java
URL: http://svn.apache.org/viewcvs/incubator/openjpa/trunk/openjpa-lib/java/org/apache/openjpa/lib/util/Files.java?rev=406215&view=auto
==============================================================================
--- incubator/openjpa/trunk/openjpa-lib/java/org/apache/openjpa/lib/util/Files.java (added)
+++ incubator/openjpa/trunk/openjpa-lib/java/org/apache/openjpa/lib/util/Files.java Sat May 13 20:25:56 2006
@@ -0,0 +1,296 @@
+/*
+ * Copyright 2006 The Apache Software Foundation.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *     http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package org.apache.openjpa.lib.util;
+
+
+import java.io.*;
+import java.net.*;
+
+import org.apache.commons.lang.exception.*;
+
+import serp.util.*;
+
+
+/**
+ *	<p>Utility operations on files.</p>
+ *
+ *	@author		Abe White
+ *	@nojavadoc
+ */
+public class Files
+{
+	/**
+	 *	Backup the given file to a new file called &lt;file-name&gt;~.  If
+	 *	the file does not exist or a backup could not be created, returns null.
+	 */
+	public static File backup (File file, boolean copy)
+	{
+		if (file == null || !file.exists ())
+			return null;
+
+		// create new file object copy so we don't modify the original
+		File clone = new File (file.getAbsolutePath ());
+		File bk = new File (file.getAbsolutePath () + "~");
+		if (!clone.renameTo (bk))
+			return null;
+		if (copy)
+		{
+			try
+			{	
+				copy (bk, file);
+			}
+			catch (IOException ioe)
+			{
+				throw new NestableRuntimeException (ioe);
+			}
+		}
+		return bk;
+	}
+
+
+	/**
+	 *	Revert the given backup file to the original location.  If the given
+	 *	file's name does not end in '~', the '~' is appended before proceeding.
+	 *	If the backup file does not exist or could not be reverted, 
+	 *	returns null.
+	 */
+	public static File revert (File backup, boolean copy)
+	{
+		if (backup == null)
+			return null;
+		if (!backup.getName ().endsWith ("~"))
+			backup = new File (backup.getPath () + "~");
+		if (!backup.exists ())
+			return null;
+
+		// create new file object copy so we don't modify the original
+		String path = backup.getAbsolutePath ();
+		File clone = new File (path);
+		File orig = new File (path.substring (0, path.length () - 1));
+		if (!clone.renameTo (orig))
+			return null;
+		if (copy)
+		{
+			try
+			{	
+				copy (orig, backup);
+			}
+			catch (IOException ioe)
+			{
+				throw new NestableRuntimeException (ioe);
+			}
+		}
+		return orig;
+	}
+
+
+	/**
+	 *	Return the source file for the given class, or null if the
+	 *	source is not in the CLASSPATH.
+	 */
+	public static File getSourceFile (Class cls)
+	{
+		return getClassFile (cls, ".java");
+	}
+
+
+	/**
+	 *	Return the class file of the given class, or null if the
+	 *	class is in a jar.
+ 	 */
+	public static File getClassFile (Class cls)
+	{
+		return getClassFile (cls, ".class");
+	}
+
+
+	/**
+	 *	Return the file for the class resource with the given extension.
+	 */
+	private static File getClassFile (Class cls, String ext)
+	{
+		String name = Strings.getClassName (cls);
+
+		// if it's an inner class, use the parent class name
+		int innerIdx = name.indexOf ('$');
+		if (innerIdx != -1)
+			name = name.substring (0, innerIdx);
+		
+		URL rsrc = cls.getResource (name + ext);
+		if (rsrc != null && rsrc.getProtocol ().equals ("file"))	
+			return new File (URLDecoder.decode (rsrc.getFile ()));
+		return null;
+	}
+
+
+	/**
+ 	 *	Return the file for the given package.  If the given base directory 
+	 *	matches the given package structure, it will be used as-is.  If not, 
+	 *	the package structure will be added beneath the base directory.  If
+	 *	the base directory is null, the current working directory will be
+	 *	used as the base.
+	 */
+	public static File getPackageFile (File base, String pkg, boolean mkdirs)
+	{
+		if (base == null)
+			base = new File (System.getProperty ("user.dir"));
+		if (pkg == null || pkg.length () == 0)
+		{
+			if (mkdirs && !base.exists ())
+				base.mkdirs ();
+			return base;
+		}
+
+		pkg = pkg.replace ('.', File.separatorChar);
+		File file = null;
+		try
+		{
+			if (base.getCanonicalPath ().endsWith (pkg))
+				file = base;
+			else
+				file = new File (base, pkg);	
+		}
+		catch (IOException ioe)
+		{
+			throw new NestableRuntimeException (ioe);
+		}
+
+		if (mkdirs && !file.exists ())
+			file.mkdirs ();
+		return file;
+	}
+
+
+	/**
+	 *	Check the given string for a matching file.  The string is first 
+	 *	tested to see if it is an existing file path.  If it does not
+	 *	represent an existing file, it is checked as a resource name of a
+	 *	file.  If no resource exists, then it is interpreted as a path
+	 *	to a file that does not exist yet.
+	 *
+	 *	@param	name	the file path or resource name
+	 *	@param	loader	a class loader to use in resource lookup, or null
+	 *					to use the thread's context loader	
+	 */
+	public static File getFile (String name, ClassLoader loader)
+	{
+		if (name == null)
+			return null;
+
+		File file = new File (name);
+		if (file.exists ())
+			return file;
+
+		if (loader == null)
+			loader = Thread.currentThread ().getContextClassLoader ();
+		URL url = loader.getResource (name);
+		if (url != null)
+		{
+			String urlFile = url.getFile ();
+			if (urlFile != null)
+			{
+				File rsrc = new File (URLDecoder.decode (urlFile));
+				if (rsrc.exists ())
+					return rsrc;
+			}
+		}
+
+		// go back to original non-existant file path
+		return file;
+	}
+
+
+	/**
+	 *	Return a writer to the stream (stdout or stderr) or file named by the 
+	 *	given string.
+	 *
+	 *	@see	#getFile
+	 */
+	public static Writer getWriter (String file, ClassLoader loader)
+		throws IOException
+	{
+		if (file == null)
+			return null;
+		if ("stdout".equals (file))
+			return new PrintWriter (System.out);
+		if ("stderr".equals (file))
+			return new PrintWriter (System.err);
+		try
+		{
+			return new FileWriter (getFile (file, loader));
+		}
+		catch (IOException ioe)
+		{
+			throw new NestableRuntimeException (ioe);
+		}
+	}
+
+
+	/**
+	 *	Return an output stream to the stream (stdout or stderr) or file named 
+	 *	by the given string.
+	 *
+	 *	@see	#getFile
+	 */
+	public static OutputStream getOutputStream (String file, ClassLoader loader)
+	{
+		if (file == null)
+			return null;
+		if ("stdout".equals (file))
+			return System.out;
+		if ("stderr".equals (file))
+			return System.err;
+		try
+		{
+			return new FileOutputStream (getFile (file, loader));
+		}
+		catch (IOException ioe)
+		{
+			throw new NestableRuntimeException (ioe);
+		}
+	}
+
+
+	/**
+	 *	Copy a file.  Return false if <code>from</code> does not exist.
+	 */
+	public static boolean copy (File from, File to)
+		throws IOException
+	{
+		if (from == null || to == null || !from.exists ())
+			return false;
+		
+		FileInputStream in = null;
+		FileOutputStream out = null;
+		try
+		{
+			in = new FileInputStream (from);
+			BufferedInputStream inbuf = new BufferedInputStream (in);
+			out = new FileOutputStream (to);
+			BufferedOutputStream outbuf = new BufferedOutputStream (out);
+			for (int b; (b = inbuf.read ()) != -1; outbuf.write (b));
+			outbuf.flush ();
+			return true;
+		}
+		finally
+		{
+			if (in != null)
+				try { in.close (); } catch (Exception e) {}
+			if (out != null)
+				try { out.close (); } catch (Exception e) {}
+		}
+	}
+}

Propchange: incubator/openjpa/trunk/openjpa-lib/java/org/apache/openjpa/lib/util/Files.java
------------------------------------------------------------------------------
    svn:executable = *

Added: incubator/openjpa/trunk/openjpa-lib/java/org/apache/openjpa/lib/util/FormatPreservingProperties.java
URL: http://svn.apache.org/viewcvs/incubator/openjpa/trunk/openjpa-lib/java/org/apache/openjpa/lib/util/FormatPreservingProperties.java?rev=406215&view=auto
==============================================================================
--- incubator/openjpa/trunk/openjpa-lib/java/org/apache/openjpa/lib/util/FormatPreservingProperties.java (added)
+++ incubator/openjpa/trunk/openjpa-lib/java/org/apache/openjpa/lib/util/FormatPreservingProperties.java Sat May 13 20:25:56 2006
@@ -0,0 +1,842 @@
+/*
+ * Copyright 2006 The Apache Software Foundation.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *     http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package org.apache.openjpa.lib.util;
+
+import java.io.*;
+import java.util.*;
+
+import org.apache.commons.collections.set.*;
+
+/*
+ *	### things to add:
+ * 	- should probably be a SourceTracker
+ *
+ * 	- if an entry is removed, should there be an option to remove comments
+ *	  just before the entry (a la javadoc)?
+ *
+ *	- should we have an option to clean up whitespace?
+ *
+ *	- potentially would be interesting to add comments about each
+ *	  property that OpenJPA adds to this object. We'd want to make the
+ *	  automatic comment-removing code work first, though, so that if
+ *	  someone then removed the property, the comments would go away.
+ *
+ *	- would be neat if DuplicateKeyException could report line numbers of
+ *	  offending entries.
+ *
+ *	- putAll() with another FormatPreservingProperties should be smarter
+ */
+/**
+ *	<p>A specialization of {@link Properties} that stores its contents
+ *	in the same order and with the same formatting as was used to read
+ *	the contents from an input stream. This is useful because it means
+ *	that a properties file loaded via this object and then written
+ *	back out later on will only be different where changes or
+ *	additions were made.</p>
+ *
+ *	<p>By default, the {@link #store} method in this class does not
+ *	behave the same as {@link Properties#store}. You can cause an
+ *	instance to approximate the behavior of {@link Properties#store}
+ *	by invoking {@link #setDefaultEntryDelimiter} with <code>=</code>,
+ *	{@link #setAddWhitespaceAfterDelimiter} with <code>false</code>, and
+ *	{@link #setAllowDuplicates} with <code>true</code>. However, this
+ *	will only influence how the instance will write new values, not how
+ *	it will write existing key-value pairs that are modified.</p>
+ *	
+ *	<p>In conjunction with a conservative output writer, it is
+ *	possible to only write to disk changes / additions.</p>
+ *
+ *	<p>This implementation does not permit escaped ' ', '=', ':'
+ * 	characters in key names.</p>
+ *	
+ *	@since 3.3
+ */
+public class FormatPreservingProperties
+	extends Properties
+{
+	private static Localizer _loc = Localizer.forPackage
+		(FormatPreservingProperties.class);
+
+	private char defaultEntryDelimiter = ':';
+	private boolean addWhitespaceAfterDelimiter = true;
+	private boolean allowDuplicates = false;
+	private boolean insertTimestamp = false;
+
+	private PropertySource source;
+	private ListOrderedSet newKeys = new ListOrderedSet ();
+	private HashSet modifiedKeys = new HashSet ();
+
+	// marker that indicates that we're not deserializing
+	private transient boolean isNotDeserializing = true;
+	private transient boolean isLoading = false;
+
+
+	public FormatPreservingProperties ()
+	{
+		this (null);
+	}
+
+
+	public FormatPreservingProperties (Properties defaults)
+	{
+		super (defaults);
+	}
+
+
+	/** 
+	 *  The character to use as a delimiter between property keys and values. 
+	 *  
+	 *  @param  defaultEntryDelimiter  either ':' or '='
+	 */
+	public void setDefaultEntryDelimiter (char defaultEntryDelimiter)
+	{
+		this.defaultEntryDelimiter = defaultEntryDelimiter;
+	}
+
+
+	/** 
+	 *  See {@link #setDefaultEntryDelimiter} 
+	 */
+	public char getDefaultEntryDelimiter ()
+	{
+		return this.defaultEntryDelimiter;
+	}
+
+
+	/**
+	 *	<p>If set to <code>true</code>, this properties object will add a
+	 *	space after the delimiter character (if the delimiter is not
+	 *	the space character). Else, this will not add a
+	 *	space.</p>
+	 *
+	 *	<p>Default value: <code>true</code>. Note that {@link
+	 *	Properties#store} never writes whitespace.</p>
+	 */
+	public void setAddWhitespaceAfterDelimiter (boolean add)
+	{
+		this.addWhitespaceAfterDelimiter = add;
+	}
+
+
+	/**
+	 *	<p>If set to <code>true</code>, this properties object will add a
+	 *	space after the delimiter character (if the delimiter is not
+	 *	the space character). Else, this will not add a
+	 *	space.</p>
+	 *
+	 *	<p>Default value: <code>true</code>. Note that {@link
+	 *	Properties#store} never writes whitespace.</p>
+	 */
+	public boolean getAddWhitespaceAfterDelimiter ()
+	{
+		return this.addWhitespaceAfterDelimiter;
+	}
+
+
+	/**
+	 *	<p>If set to <code>true</code>, this properties object will add a
+	 *	timestamp to the beginning of the file, just after the header
+	 *	(if any) is printed. Else, this will not add a
+	 *	timestamp.</p>
+	 *
+	 * 	<p>Default value: <code>false</code>. Note that {@link
+	 *	Properties#store} always writes a timestamp.</p>
+	 */
+	public void setInsertTimestamp (boolean insertTimestamp)
+	{
+		this.insertTimestamp = insertTimestamp;
+	}
+
+
+	/**
+	 *	<p>If set to <code>true</code>, this properties object will add a
+	 *	timestamp to the beginning of the file, just after the header
+	 *	(if any) is printed. Else, this will not add a
+	 *	timestamp.</p>
+	 *
+	 * 	<p>Default value: <code>false</code>. Note that {@link
+	 *	Properties#store} always writes a timestamp.</p>
+	 */
+	public boolean getInsertTimestamp ()
+	{
+		return this.insertTimestamp;
+	}
+
+
+	/**
+	 *	<p>If set to <code>true</code>, duplicate properties are allowed, and
+	 *	the last property setting in the input will overwrite any previous
+	 *	settings. If set to <code>false</code>, duplicate property definitions
+	 *	in the input will cause an exception to be thrown during
+	 *	{@link #load}.</p>
+	 *
+	 * 	<p>Default value: <code>false</code>. Note that {@link
+	 *	Properties#store} always allows duplicates.</p>
+	 */
+	public void setAllowDuplicates (boolean allowDuplicates)
+	{
+		this.allowDuplicates = allowDuplicates;
+	}
+
+
+	/**
+	 *	<p>If set to <code>true</code>, duplicate properties are allowed, and
+	 *	the last property setting in the input will overwrite any previous
+	 *	settings. If set to <code>false</code>, duplicate property definitions
+	 *	in the input will cause an exception to be thrown during
+	 *	{@link #load}.</p>
+	 *
+	 * 	<p>Default value: <code>false</code>. Note that {@link
+	 *	Properties#store} always allows duplicates.</p>
+	 */
+	public boolean getAllowDuplicates ()
+	{
+		return this.allowDuplicates;
+	}
+
+
+	public String getProperty (String key)
+	{
+		return super.getProperty (key);
+	}
+
+
+	public String getProperty (String key, String defaultValue)
+	{
+		return super.getProperty (key, defaultValue);
+	}
+
+
+	public Object setProperty (String key, String value)
+	{
+		return put (key, value);
+	}
+
+
+	/**
+	 *	Circumvents the superclass {@link #putAll} implementation,
+	 *	putting all the key-value pairs via {@link #put}.
+	 */
+	public void putAll (Map m)
+	{
+		Map.Entry e;
+		for (Iterator iter = m.entrySet ().iterator (); iter.hasNext (); )
+		{
+			e = (Map.Entry) iter.next ();
+			put (e.getKey (), e.getValue ());
+		}
+	}
+
+
+	/**
+	 *	Removes the key from the bookkeeping collectiotns as well.
+	 */
+	public Object remove (Object key)
+	{
+		newKeys.remove (key);
+		return super.remove (key);
+	}
+
+
+	public void clear ()
+	{
+		super.clear ();
+
+		if (source != null)
+			source.clear ();
+
+		newKeys.clear ();
+		modifiedKeys.clear ();
+	}
+
+
+	public Object clone ()
+	{
+		FormatPreservingProperties c = (FormatPreservingProperties) 
+			super.clone ();
+
+		if (source != null)
+			c.source = (PropertySource)source.clone ();
+
+		if (modifiedKeys != null)
+			c.modifiedKeys = (HashSet)modifiedKeys.clone ();
+
+		if (newKeys != null)
+		{
+			c.newKeys = new ListOrderedSet ();
+			c.newKeys.addAll (newKeys);
+		}
+
+		return c;
+	}
+
+
+	private void writeObject (ObjectOutputStream out)
+		throws IOException
+	{
+		out.defaultWriteObject ();
+	}
+
+
+	private void readObject (ObjectInputStream in)
+		throws IOException, ClassNotFoundException
+	{
+		in.defaultReadObject ();
+
+		isNotDeserializing = true;
+	}
+
+
+	public Object put (Object key, Object val)
+	{
+		Object o = super.put (key, val);
+
+		// if we're no longer loading from properties and this put
+		// represents an actual change in value, mark the modification
+		// or addition in the bookkeeping collections.
+		if (!isLoading && isNotDeserializing && !val.equals (o))
+		{
+			if (o != null)
+				modifiedKeys.add (key);
+			else if (!newKeys.contains (key))
+				newKeys.add (key);
+		}
+		return o;
+	}
+
+
+
+	/**
+	 *  Loads the properties in <code>in</code>, according to the rules
+	 *  described in {@link Properties#load}. If {@link #getAllowDuplicates}
+	 *  returns <code>true</code>, this will throw a {@link
+	 *  DuplicateKeyException} if duplicate property declarations are
+	 *  encountered.
+	 *
+	 *  @see Properties#load
+	 */
+	public void load (InputStream in)
+		throws IOException
+	{
+		isLoading = true;
+		try
+		{
+			loadProperties (in);
+		}
+		finally
+		{
+			isLoading = false;
+		}
+	}
+
+
+	private void loadProperties (InputStream in)
+		throws IOException
+	{
+		source = new PropertySource ();
+
+		PropertyLineReader reader = new PropertyLineReader (in, source);
+
+		Set loadedKeys = new HashSet ();
+
+		for (PropertyLine l;
+			(l = reader.readPropertyLine ()) != null && source.add (l); )
+		{
+			String line = l.line.toString ();
+
+			char c = 0;
+			int pos = 0;
+
+			while (pos < line.length () && isSpace (c = line.charAt (pos)))
+				pos++;
+
+			if ((line.length () - pos) == 0
+				|| line.charAt (pos) == '#' || line.charAt (pos) == '!')
+				continue;
+
+			StringBuffer key = new StringBuffer ();
+			while (pos < line.length () && !isSpace (c = line.charAt (pos++))
+				&& c != '=' && c != ':')
+			{
+				if (c == '\\')
+				{
+					if (pos == line.length ()) // end of line: read the next
+					{
+						l.append (line = reader.readLine ());
+						pos = 0;
+						while (pos < line.length ()
+							&& isSpace (c = line.charAt (pos)))
+							pos++;
+					}
+					else
+					{
+						pos = readEscape (line, pos, key);
+					}
+				}
+				else
+				{
+					key.append (c);
+				}
+			}
+
+			boolean isDelim = (c == ':' || c == '=');
+
+			for ( ; pos < line.length ()
+				&& isSpace (c = line.charAt (pos)); pos++);
+
+			if (!isDelim && (c == ':' || c == '='))
+			{
+				pos++;
+				while (pos < line.length () && isSpace (c = line.charAt (pos)))
+					pos++;
+			}
+
+			StringBuffer element = new StringBuffer (line.length () - pos);
+
+			while (pos < line.length ())
+			{
+				c = line.charAt (pos++);
+				if (c == '\\')
+				{
+					if (pos == line.length ()) // end of line: read the next
+					{
+						l.append (line = reader.readLine ());
+
+						if (line == null)
+							break;
+
+						pos = 0;
+						while (pos < line.length ()
+							&& isSpace (c = line.charAt (pos)))
+							pos++;
+						element.ensureCapacity (line.length () - pos +
+							element.length ());
+					}
+					else
+					{
+						pos = readEscape (line, pos, element);
+					}
+				}
+				else
+					element.append (c);
+			}
+
+			if (!loadedKeys.add (key.toString ()) && !allowDuplicates)
+				throw new DuplicateKeyException (key.toString (), 
+					getProperty (key.toString ()), element.toString ());
+
+			l.setPropertyKey (key.toString ());
+			l.setPropertyValue (element.toString ());
+			put (key.toString (), element.toString ());
+		}
+	}
+
+
+	/** 
+	 *  Read the next escaped character: handle newlines, tabs, returns, and
+	 *  form feeds with the appropriate escaped character, then try to
+	 *  decode unicode characters. Finally, just add the character
+	 *  explicitly.
+	 *  
+	 *  @param  source  the source of the characters 
+	 *  @param  pos     the position at which to start reading
+	 *  @param  value   the value we are appending to
+	 *  @return         the position after the reading is done
+	 */
+	private static int readEscape (String source, int pos, StringBuffer value)
+	{
+		char c = source.charAt (pos++);
+		switch (c)
+		{
+		case 'n':
+			value.append ('\n');
+			break;
+		case 't':
+			value.append ('\t');
+			break;
+		case 'f':
+			value.append ('\f');
+			break;
+		case 'r':
+			value.append ('\r');
+			break;
+		case 'u':
+			if (pos + 4 <= source.length ())
+			{
+				char uni = (char)Integer.parseInt
+					(source.substring (pos, pos + 4), 16);
+				value.append (uni);
+				pos += 4;
+			}
+			break;
+		default:
+			value.append (c);
+			break;
+		}
+
+		return pos;
+	}
+
+
+	private static boolean isSpace (char ch)
+	{
+		return Character.isWhitespace (ch);
+	}
+
+
+	public void save (OutputStream out, String header)
+	{
+		try
+		{
+			store (out, header);
+		}
+		catch (IOException ex)
+		{
+		}
+	}
+
+
+	public void store (OutputStream out, String header)
+		throws IOException
+	{
+		boolean endWithNewline = source != null && source.endsInNewline;
+		boolean firstLine = true;
+
+		// Must be ISO-8859-1 ecoding according to Properties.load javadoc
+		PrintWriter writer = new PrintWriter
+			(new OutputStreamWriter (out, "ISO-8859-1"), false);
+
+		if (header != null)
+			writer.println ("#" + header);
+
+		if (insertTimestamp)
+			writer.println ("#" + Calendar.getInstance ().getTime ());
+
+		List lines = new LinkedList ();
+		// first write all the existing props as they were initially read
+		if (source != null)
+			lines.addAll (source);
+
+		// next write out new keys, then the rest of the keys
+		ListOrderedSet keys = new ListOrderedSet ();
+		keys.addAll (newKeys);
+		keys.addAll (keySet ());
+
+		lines.addAll (keys);
+
+		keys.remove (null);
+
+		boolean needsNewline = false;
+
+		for (Iterator i = lines.iterator (); i.hasNext (); )
+		{
+			Object next = i.next ();
+
+			if (next instanceof PropertyLine)
+			{
+				if (((PropertyLine)next).write (writer, keys, needsNewline))
+					needsNewline = i.hasNext ();
+			}
+			else if (next instanceof String)
+			{
+				String key = (String)next;
+				if (keys.remove (key))
+				{
+					if (writeProperty (key, writer, needsNewline))
+					{
+						needsNewline = i.hasNext () && keys.size () > 0;
+
+						// any new or modified properties will cause
+						// the file to end with a newline
+						endWithNewline = true;
+					}
+				}
+			}
+		}
+
+		// make sure we end in a newline if the source ended in it
+		if (endWithNewline)
+			writer.println ();
+
+		writer.flush ();
+	}
+
+
+	private boolean writeProperty (String key, PrintWriter writer,
+		boolean needsNewline)
+	{
+		StringBuffer s = new StringBuffer ();
+
+		if (key == null)
+			return false;
+
+		String val = getProperty (key);
+		if (val == null)
+			return false;
+
+		formatValue (key, s, true);
+		s.append (defaultEntryDelimiter);
+		if (addWhitespaceAfterDelimiter)
+			s.append (' ');
+		formatValue (val, s, false);
+
+		if (needsNewline)
+			writer.println ();
+
+		writer.print (s);
+
+		return true;
+	}
+
+
+	/** 
+	 *  Format the given string as an encoded value for storage. This will
+	 *  perform any necessary escaping of special characters.
+	 *  
+	 *  @param  str    the value to encode
+	 *  @param  buf    the buffer to which to append the encoded value
+	 *  @param  isKey  if true, then the string is a Property key, otherwise
+	 *                 it is a value
+	 */
+	private static void formatValue (String str, StringBuffer buf,
+		boolean isKey)
+	{
+		if (isKey)
+		{
+			buf.setLength (0);
+			buf.ensureCapacity (str.length ());
+		}
+		else
+		{
+			buf.ensureCapacity (buf.length () + str.length ());
+		}
+
+		boolean escapeSpace = true;
+		int size = str.length ();
+
+		for (int i = 0; i < size; i++)
+		{
+			char c = str.charAt (i);
+
+			if (c == '\n')
+				buf.append ("\\n");
+			else if (c == '\r')
+				buf.append ("\\r");
+			else if (c == '\t')
+				buf.append ("\\t");
+			else if (c == '\f')
+				buf.append ("\\f");
+			else if (c == ' ')
+				buf.append (escapeSpace ? "\\ " : " ");
+			else if (c == '\\' || c == '!' || c == '#' || c == '=' || c == ':')
+				buf.append ('\\').append (c);
+			else if (c < ' ' || c > '~')
+				buf.append ("\\u0000".substring (0, 6 - Integer.toHexString (c).
+					length ())).append (Integer.toHexString (c));
+			else
+				buf.append (c);
+
+			if (c != ' ')
+				escapeSpace = isKey;
+		}
+	}
+
+
+	public static class DuplicateKeyException
+		extends RuntimeException
+	{       
+		public DuplicateKeyException (String key, Object firstVal,
+			String secondVal)
+		{
+			super (_loc.get ("dup-key", key, firstVal, secondVal));
+		}
+	}
+
+
+	/** 
+	 *  Contains the original line of the properties file: can be a
+	 *  proper key/value pair, or a comment, or just whitespace. 
+	 */
+	private class PropertyLine
+		implements Serializable
+	{
+		private final StringBuffer line = new StringBuffer ();
+		private String propertyKey;
+		private String propertyValue;
+
+
+		public PropertyLine (String line)
+		{
+			this.line.append (line);
+		}
+
+
+		public void append (String newline)
+		{
+			line.append (System.getProperty ("line.separator"));
+			line.append (newline);
+		}
+
+
+		public void setPropertyKey (String propertyKey)
+		{
+			this.propertyKey = propertyKey;
+		}
+
+
+		public String getPropertyKey ()
+		{
+			return this.propertyKey;
+		}
+
+
+		public void setPropertyValue (String propertyValue)
+		{
+			this.propertyValue = propertyValue;
+		}
+
+
+		public String getPropertyValue ()
+		{
+			return this.propertyValue;
+		}
+
+
+		/** 
+		 *  Write the given line. It will only be written if the line is a
+		 *  comment, or if it is a property and its value is unchanged
+		 *  from the original.
+		 *  
+		 *  @param  pw  the PrintWriter to which the write
+		 *  @return whether or not this was a known key
+		 */
+		public boolean write (PrintWriter pw, Collection keys,
+			boolean needsNewline)
+		{
+			// no property? It may be a comment or just whitespace
+			if (propertyKey == null)
+			{
+				if (needsNewline)
+					pw.println ();
+				pw.print (line.toString ());
+				return true;
+			}
+
+			// check to see if we are the same value we initially read:
+			// if so, then just write it back exactly as it was read
+			if (propertyValue != null && containsKey (propertyKey) &&
+				(propertyValue.equals (getProperty (propertyKey)) ||
+				 	(!newKeys.contains (propertyKey) &&
+					!modifiedKeys.contains (propertyKey))))
+			{
+				if (needsNewline)
+					pw.println ();
+				pw.print (line.toString ());
+
+				keys.remove (propertyKey);
+
+				return true;
+			}
+
+			// if we have modified or added the specified key, then write
+			// it back to the same location in the file from which it
+			// was originally read, so that it will be in the proximity
+			// to the comment
+			if (containsKey (propertyKey) &&
+				(modifiedKeys.contains (propertyKey) ||
+				 	newKeys.contains (propertyKey)))
+			{
+				while (keys.remove (propertyKey));
+				return writeProperty (propertyKey, pw, needsNewline);
+			}
+
+			// this is a new or changed property: don't do anything
+			return false;
+		}
+	}
+
+
+	private class PropertyLineReader
+		extends BufferedReader
+	{
+		public PropertyLineReader (InputStream in, PropertySource source)
+			throws IOException
+		{
+			// Must be ISO-8859-1 ecoding according to Properties.load javadoc
+			super (new InputStreamReader (new LineEndingStream (in, source),
+				"ISO-8859-1"));
+		}
+
+
+		public PropertyLine readPropertyLine ()
+			throws IOException
+		{
+			String l = readLine ();
+			if (l == null)
+				return null;
+
+			PropertyLine pl = new PropertyLine (l);
+			return pl;
+		}
+	}
+
+
+	/** 
+	 *  Simple FilterInputStream that merely remembers if the last
+	 *  character that it read was a newline or not. 
+	 */
+	private static class LineEndingStream
+		extends FilterInputStream
+	{
+		private final PropertySource source;
+
+
+		LineEndingStream (InputStream in, PropertySource source) 
+		{
+			super (in);
+
+			this.source = source;
+		}
+
+
+		public int read ()
+			throws IOException
+		{
+			int c = super.read ();
+			source.endsInNewline = (c == '\n' || c == '\r');
+			return c;
+		}
+
+
+		public int read (byte[] b, int off, int len)
+			throws IOException
+		{
+			int n = super.read (b, off, len);
+			if (n > 0)
+				source.endsInNewline =
+					(b[n + off - 1] == '\n' || b[n + off - 1] == '\r');
+			return n;
+		}
+	}
+
+
+	static class PropertySource
+		extends LinkedList
+		implements Cloneable, Serializable
+	{
+		private boolean endsInNewline = false;
+	}
+}

Propchange: incubator/openjpa/trunk/openjpa-lib/java/org/apache/openjpa/lib/util/FormatPreservingProperties.java
------------------------------------------------------------------------------
    svn:executable = *

Added: incubator/openjpa/trunk/openjpa-lib/java/org/apache/openjpa/lib/util/JavaVersions.java
URL: http://svn.apache.org/viewcvs/incubator/openjpa/trunk/openjpa-lib/java/org/apache/openjpa/lib/util/JavaVersions.java?rev=406215&view=auto
==============================================================================
--- incubator/openjpa/trunk/openjpa-lib/java/org/apache/openjpa/lib/util/JavaVersions.java (added)
+++ incubator/openjpa/trunk/openjpa-lib/java/org/apache/openjpa/lib/util/JavaVersions.java Sat May 13 20:25:56 2006
@@ -0,0 +1,316 @@
+/*
+ * Copyright 2006 The Apache Software Foundation.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *     http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package org.apache.openjpa.lib.util;
+
+
+import java.lang.reflect.*;
+import java.util.*;
+
+
+/**
+ *	<p>Utilities for dealing with different Java spec versions.</p>
+ *
+ *	@author		Abe White
+ *	@nojavadoc
+ */
+public class JavaVersions
+{
+	/**
+	 *	Java version; one of 2, 3, 4, 5, 6, or 7.
+	 */
+	public static final int VERSION;
+
+	private static final Class[] EMPTY_CLASSES = new Class[0];
+
+	private static Class 	PARAM_TYPE	= null;
+	private static Class 	ENUM_TYPE	= null;
+	private static Class 	ANNO_TYPE	= null;
+	private static Method	GET_STACK	= null;
+	private static Method	SET_STACK	= null;
+	private static Method	GET_CAUSE	= null;
+	private static Method	INIT_CAUSE	= null;
+
+	static
+	{
+		String specVersion = System.getProperty ("java.specification.version");
+		if ("1.2".equals (specVersion))
+			VERSION = 2;
+		else if ("1.3".equals (specVersion))
+			VERSION = 3;
+		else if ("1.4".equals (specVersion))
+			VERSION = 4;
+		else if ("1.5".equals (specVersion))
+			VERSION = 5;
+		else if ("1.6".equals (specVersion))
+			VERSION = 6;
+		else
+			VERSION = 7; // maybe someday...
+
+		if (VERSION >= 5)
+		{
+			try
+			{
+				PARAM_TYPE = Class.forName 
+					("java.lang.reflect.ParameterizedType");
+				ENUM_TYPE = Class.forName ("java.lang.Enum");
+				ANNO_TYPE = Class.forName ("java.lang.annotation.Annotation");
+			}
+			catch (Throwable t)
+			{
+			}
+		}
+
+		if (VERSION >= 4)
+		{
+			try
+			{
+				Class stack = Class.forName ("[Ljava.lang.StackTraceElement;");
+				GET_STACK = Throwable.class.getMethod ("getStackTrace", 
+					(Class[]) null);
+				SET_STACK = Throwable.class.getMethod ("setStackTrace",
+					new Class[] { stack });
+				GET_CAUSE = Throwable.class.getMethod ("getCause",
+					(Class[]) null);
+				INIT_CAUSE = Throwable.class.getMethod ("initCause",
+					new Class[] { Throwable.class });
+			}
+			catch (Throwable t)
+			{
+			}
+		}
+	}
+
+
+	/** 
+	 *  Returns a version-specific instance of the specified class
+	 *
+	 *  @see #getVersionSpecificClass(java.lang.String)
+	 *  
+	 *  @param  base  the base class to check
+	 *  @return the JDK-version-specific version of the class
+	 */
+	public static Class getVersionSpecificClass (Class base)
+	{
+		try
+		{
+			return getVersionSpecificClass (base.getName ());
+		}
+		catch (ClassNotFoundException e)
+		{
+			return base;
+		}
+	}
+
+
+	/** 
+	 *  Obtains a subclass of the specific base class that is
+	 *  specific to the current version of Java in use. The 
+	 *  heuristic for the class name to load will be that OpenJPA
+	 *  first checks for the name of the class with the current
+	 *  setting of the {@link #VERSION} field, then each number in
+	 *  decreasing order, until ending in the unqualified name.
+	 *  <p>
+	 *  For example, if we are using JDK 1.5.1, and we want to load
+	 *  "org.apache.openjpa.lib.SomeClass", we will try to load the following
+	 *  classes in order and return the first one that is successfully
+	 *  found and loaded:
+	 *  <ol>
+	 *  <li>org.apache.openjpa.lib.SomeClass5</li>
+	 *  <li>org.apache.openjpa.lib.SomeClass4</li>
+	 *  <li>org.apache.openjpa.lib.SomeClass3</li>
+	 *  <li>org.apache.openjpa.lib.SomeClass2</li>
+	 *  <li>org.apache.openjpa.lib.SomeClass1</li>
+	 *  <li>org.apache.openjpa.lib.SomeClass</li>
+	 *  </ol>
+	 *  
+	 *  @param  base  the base name of the class to load
+	 *  @return the subclass appropriate for the current Java version
+	 */
+	public static Class getVersionSpecificClass (String base)
+		throws ClassNotFoundException
+	{
+		for (int i = VERSION; i >= 1; i--)
+		{
+			try
+			{
+				return Class.forName (base + i);
+			}
+			catch (Throwable e)
+			{
+				// throwables might occur with bytecode that we
+				// cannot understand
+			}
+		}
+		return Class.forName (base);
+	}
+
+
+	/**
+	 *	Return true if the given type is an annotation.
+	 */
+	public static boolean isAnnotation (Class cls)
+	{
+		return ANNO_TYPE != null && ANNO_TYPE.isAssignableFrom (cls);
+	}
+
+
+	/**
+	 *	Return true if the given type is an enumeration.
+	 */
+	public static boolean isEnumeration (Class cls)
+	{
+		return ENUM_TYPE != null && ENUM_TYPE.isAssignableFrom (cls);
+	}
+
+	
+	/**
+	 *	Collects the parameterized type declarations for a given field.
+	 */
+	public static Class[] getParameterizedTypes (Field f)
+	{
+		if (f == null)
+			return null;
+		if (VERSION < 5)
+			return EMPTY_CLASSES;
+
+		try
+		{
+			Object type = Field.class.getMethod ("getGenericType", 
+				(Class[]) null).invoke (f, (Object[]) null);
+			return collectParameterizedTypes (type);	
+		}
+		catch (Exception e)
+		{
+			return EMPTY_CLASSES;
+		}	
+	}
+
+	
+	/**
+	 *	Collects the parameterized return type declarations for a given method.
+	 */
+	public static Class[] getParameterizedTypes (Method meth)
+	{
+		if (meth == null)
+			return null;
+		if (VERSION < 5)
+			return EMPTY_CLASSES;
+
+		try
+		{
+			Object type = Method.class.getMethod ("getGenericReturnType", 
+				(Class[]) null).invoke (meth, (Object[]) null);
+			return collectParameterizedTypes (type);	
+		}
+		catch (Exception e)
+		{
+			return EMPTY_CLASSES;
+		}
+	}
+
+
+	/**
+	 *	Return all parameterized classes for the given type.
+	 */
+	private static Class[] collectParameterizedTypes (Object type)
+		throws Exception
+	{
+		if (PARAM_TYPE == null || !PARAM_TYPE.isInstance (type))
+			return EMPTY_CLASSES;
+
+		Object[] args = (Object[]) PARAM_TYPE.getMethod 
+			("getActualTypeArguments", (Class[]) null).invoke (type, 
+			(Object[]) null);
+		if (args.length == 0)
+			return EMPTY_CLASSES;
+
+		Class[] clss = new Class[args.length];
+		for (int i = 0; i < args.length; i++)
+		{
+			if (!(args[i] instanceof Class))
+				return EMPTY_CLASSES;
+			clss[i] = (Class) args[i];
+		}
+		return clss;
+	}
+
+
+	/**
+	 *	Transfer the stack from one throwable to another, or return
+	 *	false if it cannot be done, possibly due to an unsupported Java version.
+	 */
+	public static boolean transferStackTrace (Throwable from, Throwable to)
+	{
+		if (GET_STACK == null || SET_STACK == null || from == null 
+			|| to == null)
+			return false;
+
+		try
+		{
+			Object stack = GET_STACK.invoke (from, (Object[]) null);
+			SET_STACK.invoke (to, new Object[] { stack });
+			return true;
+		}
+		catch (Throwable t)
+		{
+			return false;
+		}
+	}
+
+
+	/**
+	 *	Return the cause of the given throwable.
+	 */
+	public static Throwable getCause (Throwable ex)
+	{
+		if (GET_CAUSE == null || ex == null)
+			return null;
+
+		try
+		{
+			return (Throwable) GET_CAUSE.invoke (ex, (Object[]) null);	
+		}
+		catch (Throwable t)
+		{
+			return null;
+		}	
+	}
+
+
+	/**
+	 *	Set the cause of the given throwable.
+	 */
+	public static Throwable initCause (Throwable ex, Throwable cause)
+	{
+		if (INIT_CAUSE == null || ex == null || cause == null)
+			return ex;
+
+		try
+		{
+			return (Throwable) INIT_CAUSE.invoke (ex, new Object[] { cause });
+		}
+		catch (Throwable t)
+		{
+			return ex;
+		}
+	}
+
+
+	public static void main (String[] args)
+	{
+		System.out.println ("Java version is: " + VERSION);
+	}
+}

Propchange: incubator/openjpa/trunk/openjpa-lib/java/org/apache/openjpa/lib/util/JavaVersions.java
------------------------------------------------------------------------------
    svn:executable = *

Added: incubator/openjpa/trunk/openjpa-lib/java/org/apache/openjpa/lib/util/Localizer.java
URL: http://svn.apache.org/viewcvs/incubator/openjpa/trunk/openjpa-lib/java/org/apache/openjpa/lib/util/Localizer.java?rev=406215&view=auto
==============================================================================
--- incubator/openjpa/trunk/openjpa-lib/java/org/apache/openjpa/lib/util/Localizer.java (added)
+++ incubator/openjpa/trunk/openjpa-lib/java/org/apache/openjpa/lib/util/Localizer.java Sat May 13 20:25:56 2006
@@ -0,0 +1,275 @@
+/*
+ * Copyright 2006 The Apache Software Foundation.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *     http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package org.apache.openjpa.lib.util;
+
+
+import java.text.*;
+import java.util.*;
+
+
+/**
+ *	<p>The Localizer provides convenient access to localized
+ *	strings.  It inlcudes built-in support for parameter substitution through
+ *	the use of the {@link MessageFormat} utility.</p>
+ *
+ *	<p>Strings are stored in per-package {@link Properties} files.  
+ *	The property file for the default locale must be named 
+ *	<code>localizer.properties</code>.   Additional locales can be supported
+ *	through additional property files using the naming conventions specified
+ *	in the {@link ResourceBundle} class.  For example, the german locale 
+ *	could be supported through a <code>localizer_de_DE.properties</code> 
+ *	file.</p>
+ *
+ *	@author		Abe White
+ */
+public class Localizer
+{
+	// static cache of package+loc name to localizer mappings
+	private static final Map _localizers = new HashMap ();
+
+	// list of resource providers to delegate to when locating resources
+	private static final List _providers = new LinkedList ();
+	static
+	{
+		_providers.add (new SimpleResourceBundleProvider ());
+		_providers.add (new StreamResourceBundleProvider ());
+		_providers.add (new ZipResourceBundleProvider ());
+	}
+
+	// the local file name and class' classloader
+	private ResourceBundle _bundle = null;
+
+
+	/**
+	 *	Return a Localizer instance that will access the properties file
+	 *	in the package of the given class using the system default locale.
+	 *
+	 *	@see	#forPackage(Class,Locale)
+	 */
+	public static Localizer forPackage (Class cls)
+	{
+		return forPackage (cls, null);
+	}
+
+
+	/**
+	 *	Return a Localizer instance that will access the properties file
+	 *	in the package of the given class using the given locale.
+ 	 *		
+	 *	@param	cls		the class whose package to check for the localized 
+	 *					properties file; if null, the system will check for
+	 *					a top-level properties file
+	 *	@param	locale	the locale to which strings should be localized; if
+	 *					null, the system default will be assumed
+	 */	
+	public static Localizer forPackage (Class cls, Locale locale)
+	{
+		if (locale == null)
+			locale = Locale.getDefault ();
+
+		int dot = (cls == null) ? -1 : cls.getName ().lastIndexOf ('.');
+		String file;
+		if (dot == -1)
+			file = "localizer";
+		else
+			file = cls.getName ().substring (0, dot + 1) + "localizer";
+		String key = file + locale.toString ();
+
+		synchronized (_localizers)
+		{
+			// check for cached version
+			Localizer loc = (Localizer) _localizers.get (key);
+			if (loc != null)
+				return loc;
+
+			// find resource bundle
+			ResourceBundle bundle = null;
+			ClassLoader loader = (cls == null) ? null : cls.getClassLoader ();
+			synchronized (_providers)
+			{
+				for (Iterator itr = _providers.iterator (); itr.hasNext ();)
+				{
+					bundle = ((ResourceBundleProvider) itr.next ()).
+						findResource (file, locale, loader);
+					if (bundle != null)
+						break;
+				}
+			}
+			
+			// cache the localizer
+			loc = new Localizer ();
+			loc._bundle = bundle;
+			_localizers.put (key, loc);
+			return loc;
+		}
+	}
+
+
+	/**
+	 *	Register a resource provider.
+	 */
+	public static void addProvider (ResourceBundleProvider provider)
+	{
+		synchronized (_providers)
+		{
+			if (!_providers.contains (provider))
+				_providers.add (provider);
+		}
+	}
+
+
+	/**
+	 *	Remove a resource provider.
+	 */
+	public static boolean removeProvider (ResourceBundleProvider provider)
+	{
+		synchronized (_providers)
+		{
+			return _providers.remove (provider);
+		}
+	}
+
+
+	/**
+	 *	Return the localized string matching the given key.
+	 */
+	public String get (String key)
+	{
+		return get (key, false);
+	}
+
+
+	/**
+	 *	Return the localized string matching the given key.
+	 */
+	public String getFatal (String key)
+	{
+		return get (key, true);
+	}
+
+
+	/**
+	 *	Return the localized string matching the given key.  The given 
+ 	 *	<code>sub</code> object will be packed into an array and substituted 
+	 *	into the found string according to the rules of the 
+	 *	{@link MessageFormat} class.
+	 *
+	 *	@see	#get(String)
+	 */
+	public String get (String key, Object sub)
+	{
+		return get (key, new Object[] { sub });
+	}
+
+
+	/**
+	 *	Return the localized string matching the given key.  The given 
+ 	 *	<code>sub</code> object will be packed into an array and substituted 
+	 *	into the found string according to the rules of the 
+	 *	{@link MessageFormat} class.
+	 *
+	 *	@see	#getFatal(String)
+	 */
+	public String getFatal (String key, Object sub)
+	{
+		return getFatal (key, new Object[] { sub });
+	}
+
+
+	/**
+	 *	Return the localized string for the given key.
+	 *	
+	 *	@see	#get(String,Object)
+	 */
+	public String get (String key, Object sub1, Object sub2)
+	{
+		return get (key, new Object[] { sub1, sub2 });
+	}
+
+
+	/**
+	 *	Return the localized string for the given key.
+	 *	
+	 *	@see	#getFatal(String,Object)
+	 */
+	public String getFatal (String key, Object sub1, Object sub2)
+	{
+		return getFatal (key, new Object[] { sub1, sub2 });
+	}
+
+
+	/**
+	 *	Return the localized string for the given key.
+	 *	
+	 *	@see	#get(String,Object)
+	 */
+	public String get (String key, Object sub1, Object sub2, Object sub3)
+	{
+		return get (key, new Object[] { sub1, sub2, sub3 });
+	}
+
+
+	/**
+	 *	Return the localized string matching the given key.  The given 
+ 	 *	<code>subs</code> objects will be substituted 
+	 *	into the found string according to the rules of the 
+	 *	{@link MessageFormat} class.
+	 *
+	 *	@see	#get(String)
+	 */
+	public String get (String key, Object[] subs)
+	{
+		String str = get (key);
+		return MessageFormat.format (str, subs);
+	}
+
+
+	/**
+	 *	Return the localized string matching the given key.  The given 
+ 	 *	<code>subs</code> objects will be substituted 
+	 *	into the found string according to the rules of the 
+	 *	{@link MessageFormat} class.
+	 *
+	 *	@see	#getFatal(String)
+	 */
+	public String getFatal (String key, Object[] subs)
+	{
+		String str = getFatal (key);
+		return MessageFormat.format (str, subs);
+	}
+
+
+	private String get (String key, boolean fatal)
+	{
+		if (_bundle == null)
+		{
+			if (fatal)
+				throw new MissingResourceException (key, key, key);
+			return key;
+		}
+
+		try
+		{
+			return _bundle.getString (key);
+		}
+		catch (MissingResourceException mre)
+		{
+			if (!fatal)
+				return key;
+			throw mre;
+		}
+	}
+}

Propchange: incubator/openjpa/trunk/openjpa-lib/java/org/apache/openjpa/lib/util/Localizer.java
------------------------------------------------------------------------------
    svn:executable = *

Added: incubator/openjpa/trunk/openjpa-lib/java/org/apache/openjpa/lib/util/MultiClassLoader.java
URL: http://svn.apache.org/viewcvs/incubator/openjpa/trunk/openjpa-lib/java/org/apache/openjpa/lib/util/MultiClassLoader.java?rev=406215&view=auto
==============================================================================
--- incubator/openjpa/trunk/openjpa-lib/java/org/apache/openjpa/lib/util/MultiClassLoader.java (added)
+++ incubator/openjpa/trunk/openjpa-lib/java/org/apache/openjpa/lib/util/MultiClassLoader.java Sat May 13 20:25:56 2006
@@ -0,0 +1,306 @@
+/*
+ * Copyright 2006 The Apache Software Foundation.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *     http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package org.apache.openjpa.lib.util;
+
+
+import java.io.*;
+import java.net.*;
+import java.util.*;
+
+
+/**
+ *	<p>Class loader type that can be configured to delegate to multiple
+ *	internal class loaders.</p>
+ *
+ *	<p>The {@link #THREAD_LOADER} constant is a marker that will be replaced
+ *	with the context loader of the current thread.</p>
+ *
+ *	@author		Abe White
+ */
+public class MultiClassLoader
+	extends ClassLoader
+{
+	/**
+	 *	Marker that will be replaced with the context loader of the current
+	 *	thread whenever it is discovered in the class loader list.
+	 */
+	public static final ClassLoader THREAD_LOADER = null;
+	
+	/**
+	 *	The standard system class loader.
+	 */
+	public static final ClassLoader SYSTEM_LOADER =
+		ClassLoader.getSystemClassLoader ();
+
+	private List _loaders = new ArrayList (5);
+
+
+	/**
+	 *	Constructor; initializes the loader with an empty list of delegates.
+	 */
+	public MultiClassLoader ()
+	{
+		super (null);
+	}
+
+
+	/**
+	 *	Construct with the class loaders of another multi loader.
+	 */
+	public MultiClassLoader (MultiClassLoader other)
+	{
+		super (null);
+		addClassLoaders (other);
+	}
+
+
+	/**
+	 *	Returns true if the list contains the given class loader or marker.
+	 */
+	public boolean containsClassLoader (ClassLoader loader)
+	{
+		return _loaders.contains (loader);
+	}
+
+
+	/**
+	 *	Return an array of all contained class loaders.
+	 */
+	public ClassLoader[] getClassLoaders ()
+	{
+		ClassLoader[] loaders = new ClassLoader[size ()];
+		ClassLoader loader;
+		Iterator itr = _loaders.iterator ();
+		for (int i = 0; i < loaders.length; i++)
+		{
+			loader = (ClassLoader) itr.next ();
+			if (loader == THREAD_LOADER)
+				loader = Thread.currentThread ().getContextClassLoader ();
+			loaders[i] = loader;
+		}
+		return loaders;
+	}
+
+
+	/**
+	 *	Return the class loader at the given index.
+	 */
+	public ClassLoader getClassLoader (int index)
+	{
+		ClassLoader loader = (ClassLoader) _loaders.get (index);
+		if (loader == THREAD_LOADER)
+			loader = Thread.currentThread ().getContextClassLoader ();
+		return loader;
+	}
+
+
+	/**
+	 *	Add the given class loader to the set of loaders that will be tried.
+	 *
+	 *	@return		true if the loader was added, false if already in the list
+	 */
+	public boolean addClassLoader (ClassLoader loader)
+	{
+		if (_loaders.contains (loader))
+			return false;
+		return _loaders.add (loader);
+	}
+
+
+	/**
+	 *	Add the given class loader at the specified index.
+	 *
+	 *	@return		true if the loader was added, false if already in the list
+	 */
+	public boolean addClassLoader (int index, ClassLoader loader)
+	{
+		if (_loaders.contains (loader))
+			return false;
+		_loaders.add (index, loader);
+		return true;
+	}
+
+
+	/**
+	 *	Set the class loaders of this loader to those of the given loader.
+	 */
+	public void setClassLoaders (MultiClassLoader multi)
+	{
+		clear ();
+		addClassLoaders (multi);
+	}
+
+
+	/**
+	 *	Adds all class loaders from the given multi loader starting at the
+	 *	given index.
+	 *
+	 *	@return 	true if any loaders were added, false if all already in list
+	 */
+	public boolean addClassLoaders (int index, MultiClassLoader multi)
+	{
+		if (multi == null)
+			return false;
+
+		// use iterator so that the thread loader is not resolved
+		boolean added = false;
+		for (Iterator itr = multi._loaders.iterator (); itr.hasNext ();)
+		{
+			if (addClassLoader (index, (ClassLoader) itr.next ()))
+			{
+				index++;
+				added = true;
+			}
+		}
+		return added;
+	}
+
+
+	/**
+	 *	Adds all the class loaders from the given multi loader.
+	 *
+	 *	@return 	true if any loaders were added, false if all already in list
+	 */
+	public boolean addClassLoaders (MultiClassLoader multi)
+	{
+		if (multi == null)
+			return false;
+
+		// use iterator so that the thread loader is not resolved
+		boolean added = false;
+		for (Iterator itr = multi._loaders.iterator (); itr.hasNext ();)
+			added = addClassLoader ((ClassLoader) itr.next ()) || added;	
+		return added;
+	}
+
+
+	/**
+	 *	Remove the given loader from the list.
+	 *
+	 *	@return		true if removed, false if not in list
+	 */
+	public boolean removeClassLoader (ClassLoader loader)
+	{
+		return _loaders.remove (loader);
+	}
+
+
+	/**
+	 *	Clear the list of class loaders.
+	 */
+	public void clear ()
+	{
+		_loaders.clear ();
+	}
+
+
+	/**
+	 *	Return the number of internal class loaders.
+	 */
+	public int size ()
+	{
+		return _loaders.size ();
+	}
+
+
+	/**
+	 *	Return true if there are no internal class laoders.
+	 */
+	public boolean isEmpty ()
+	{
+		return _loaders.isEmpty ();
+	}
+
+
+	protected Class findClass (String name)
+		throws ClassNotFoundException
+	{
+		ClassLoader loader;
+		for (Iterator itr = _loaders.iterator (); itr.hasNext ();)
+		{
+			loader = (ClassLoader) itr.next ();
+			if (loader == THREAD_LOADER)
+				loader = Thread.currentThread ().getContextClassLoader ();
+			try
+			{
+				return Class.forName (name, false, loader);
+			}
+			catch (Throwable t)
+			{
+			}	
+		}
+		throw new ClassNotFoundException (name);
+	} 
+
+	
+	protected URL findResource (String name)
+	{
+		ClassLoader loader;
+		URL rsrc;
+		for (Iterator itr = _loaders.iterator (); itr.hasNext ();)
+		{
+			loader = (ClassLoader) itr.next ();
+			if (loader == THREAD_LOADER)
+				loader = Thread.currentThread ().getContextClassLoader ();
+
+			rsrc = loader.getResource (name);
+			if (rsrc != null)
+				return rsrc;
+		}
+		return null;
+	}
+
+	
+	protected Enumeration findResources (String name)
+		throws IOException
+	{
+		ClassLoader loader;
+		Enumeration rsrcs;
+		Object rsrc;
+		Vector all = new Vector ();
+		for (Iterator itr = _loaders.iterator (); itr.hasNext ();)
+		{
+			loader = (ClassLoader) itr.next ();
+			if (loader == THREAD_LOADER)
+				loader = Thread.currentThread ().getContextClassLoader ();
+
+			rsrcs = loader.getResources (name);
+			while (rsrcs.hasMoreElements ())
+			{
+				rsrc = rsrcs.nextElement ();
+				if (!all.contains (rsrc))
+					all.addElement (rsrc);
+			}
+		}
+		return all.elements ();
+	}
+
+
+	public boolean equals (Object other)
+	{
+		if (other == this)
+			return true;
+		if (!(other instanceof MultiClassLoader))
+			return false;
+		return ((MultiClassLoader) other)._loaders.equals (_loaders);
+	}
+
+
+	public int hashCode ()
+	{
+		return _loaders.hashCode ();
+	}
+}

Propchange: incubator/openjpa/trunk/openjpa-lib/java/org/apache/openjpa/lib/util/MultiClassLoader.java
------------------------------------------------------------------------------
    svn:executable = *

Added: incubator/openjpa/trunk/openjpa-lib/java/org/apache/openjpa/lib/util/Options.java
URL: http://svn.apache.org/viewcvs/incubator/openjpa/trunk/openjpa-lib/java/org/apache/openjpa/lib/util/Options.java?rev=406215&view=auto
==============================================================================
--- incubator/openjpa/trunk/openjpa-lib/java/org/apache/openjpa/lib/util/Options.java (added)
+++ incubator/openjpa/trunk/openjpa-lib/java/org/apache/openjpa/lib/util/Options.java Sat May 13 20:25:56 2006
@@ -0,0 +1,691 @@
+/*
+ * Copyright 2006 The Apache Software Foundation.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *     http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package org.apache.openjpa.lib.util;
+
+
+import java.lang.reflect.*;
+import java.util.*;
+
+import org.apache.commons.collections.*;
+import org.apache.commons.lang.*;
+
+import serp.util.*;
+
+
+/**
+ *	<p>A specialization of the {@link Properties} map type with the added
+ *	abilities to read application options from the command line and to
+ *	use bean patterns to set an object's properties via command-line the
+ *	stored mappings.</p>
+ *
+ *	<p>A typical use pattern for this class is to construct a new instance
+ *	in the <code>main</code> method, then call {@link #setFromCmdLine} with the
+ *	given args.  Next, an instanceof the class being invoked is created, and
+ *	{@link #setInto} is called with that instance as a parameter.  With this
+ *	pattern, the user can configure any bean properties of the class, or even
+ *	properties of classes reachable from the class, through the command 
+ *	line.</p>
+ *
+ *	@author		Abe White
+ *	@nojavadoc
+ */
+public class Options
+	extends TypedProperties
+{
+	// maps primitive types to the appropriate wrapper class and default value
+	private static Object[][] _primWrappers = new Object[][] { 
+		{ boolean.class, Boolean.class, Boolean.FALSE },
+		{ byte.class, Byte.class, new Byte ((byte) 0) },
+		{ char.class, Character.class, new Character ((char) 0) },
+		{ double.class, Double.class, new Double (0D) },
+		{ float.class, Float.class, new Float (0F) },
+		{ int.class, Integer.class, new Integer (0) },
+		{ long.class, Long.class, new Long (0L) },
+		{ short.class, Short.class, new Short ((short) 0) },
+	};
+
+
+	/**
+	 *	Default constructor.
+	 */
+	public Options ()
+	{
+		super ();
+	}
+
+
+	/**
+	 *	Construct the options instance with the given set of defaults.
+	 *
+	 *	@see	Properties#Properties(Properties)
+	 */
+	public Options (Properties defaults)
+	{
+		super (defaults);
+	}
+
+
+	/**
+	 *	Parses the given argument list into flag/value pairs, which are stored
+	 *	as properties.  Flags that are present without values are given
+	 *	the value "true".  If any flag is found for which there is already
+	 *	a mapping present, the existing mapping will be overwritten.
+	 *	Flags should be of the form:<br />
+	 *	<code>java Foo -flag1 value1 -flag2 value2 ... arg1 arg2 ...</code>
+	 *
+	 *	@param	args	the command-line arguments
+	 *	@return			all arguments in the original array beyond the 
+	 *					flag/value pair list
+ 	 *	@author			Patrick Linskey
+	 */
+	public String[] setFromCmdLine (String[] args)
+	{
+		if (args == null || args.length == 0)
+			return args;
+
+		String key = null;
+		String value = null;
+		List remainder = new LinkedList ();
+		for (int i = 0; i < args.length + 1; i++)
+		{
+			if (i == args.length || args[i].startsWith ("-"))
+			{
+				key = trimQuote (key);
+				if (key != null)
+				{
+					if (value != null && value.length () > 0)
+						setProperty (key, trimQuote (value));
+					else
+						setProperty (key, "true");
+				}
+
+				if (i == args.length)
+					break;
+				else
+				{
+					key = args[i].substring (1);
+					value = null;
+				}
+			}
+			else if (key != null)
+			{
+				setProperty (key, trimQuote (args[i]));
+				key = null;
+			}
+			else
+				remainder.add (args[i]);
+		}
+
+		return (String[]) remainder.toArray (new String[remainder.size ()]);
+	}
+
+
+	/**
+	 *	This method uses reflection to set all the properties in the given
+	 *	object that are named by the keys in this map. For a given key 'foo', 
+	 *	the algorithm will look for a 'setFoo' method in the given instance.  
+	 *	For a given key 'foo.bar', the algorithm will first look for a  
+	 *	'getFoo' method in the given instance, then will recurse on the return
+	 *	value of that method, now looking for the 'bar'	property.  This allows
+	 *	the setting of nested object properties.  If in the above example the 
+	 *	'getFoo' method is not present or returns null, the	algorithm will 
+	 *	look for a 'setFoo' method; if found it will constrct a new instance
+	 *	of the correct type, set it using the 'setFoo' method, then recurse on
+	 *	it as above.  Property names can be nested in this way to an arbitrary
+	 *	depth.  For setter methods that take multiple parameters, the value
+	 *	mapped to the key can use the ',' as an argument separator character.
+	 *	If not enough values are present for a given method after splitting 
+	 *	the string on ',', the remaining arguments will receive default 
+	 *	values.  All arguments are converted from string form to the
+	 *	correct type if possible (i.e. if the type is primitive,
+	 *	java.lang.Clas, or has a constructor that takes a single string
+	 *	argument).  Examples:
+	 *	<ul>	
+	 *	<li>Map Entry: <code>"age"-&gt;"12"</code><br />
+	 *		Resultant method call: <code>obj.setAge (12)</code></li>
+	 *	<li>Map Entry: <code>"range"-&gt;"1,20"</code><br />
+	 *		Resultant method call: <code>obj.setRange (1, 20)</code></li>
+	 *	<li>Map Entry: <code>"range"-&gt;"10"</code><br />
+	 *		Resultant method call: <code>obj.setRange (10, 10)</code></li>
+	 *	<li>Map Entry: <code>"brother.name"-&gt;"Bob"</code><br />
+	 *		Resultant method call: <code>obj.getBrother ().setName ("Bob")
+	 *		<code></li>
+	 *	</ul>
+	 *
+	 *	Any keys present in the map for which there is no
+	 *	corresponding property in the given object will be ignored,
+	 *	and will be returned in the {@link Map} returned by this
+	 *	method.
+	 *
+	 *	@return		a {@link Map} of key-value pairs in this object 
+	 *				for which no setters could be found.
+	 *	@throws		RuntimeException on parse error
+	 */
+	public Map setInto (Object obj)
+	{
+		// set all defaults that have no explicit value 
+		Map.Entry entry = null;
+		if (defaults != null)
+		{
+			for (Iterator itr = defaults.entrySet ().iterator(); itr.hasNext();)
+			{
+				entry = (Map.Entry) itr.next ();
+				if (!containsKey (entry.getKey ()))
+					setInto (obj, entry);
+			}
+		}
+
+		// set from main map
+		Map invalidEntries = null;
+		Map.Entry e;
+		for (Iterator itr = entrySet ().iterator (); itr.hasNext ();)
+		{
+			e = (Map.Entry) itr.next ();
+			if (!setInto (obj, e))
+			{
+				if (invalidEntries == null)
+					invalidEntries = new HashMap ();
+				invalidEntries.put (e.getKey (), e.getValue ());
+			}
+		}
+
+		return (invalidEntries == null) ? MapUtils.EMPTY_MAP : invalidEntries;
+	}
+
+
+	/**
+	 *	Sets the property named by the key of the given entry in the
+	 *	given object.
+	 *
+	 *	@return <code>true</code> if the set succeeded, or
+	 *			<code>false</code> if no method could be found for 
+	 *			this property.
+	 */
+	private boolean setInto (Object obj, Map.Entry entry)
+	{
+		if (entry.getKey () == null)
+			return false;
+
+		try
+		{
+			// look for matching parameter of object
+			Object[] match = new Object[] { obj, null };
+			if (!matchOptionToMember (entry.getKey ().toString (), match))
+				return false;
+
+			Class[] type = getType (match[1]);
+			Object[] values = new Object[type.length];
+			String[] strValues;
+			if (entry.getValue () == null)
+				strValues = new String[1];
+			else if (values.length == 1)
+				strValues = new String[] { entry.getValue ().toString () };
+			else
+				strValues = Strings.split (entry.getValue ().toString (), 
+					",", 0);
+
+			// convert the string values into parameter values, if not
+			// enough string values repeat last one for rest
+			for (int i = 0; i < strValues.length; i++)
+				values[i] = stringToObject (strValues[i].trim (), type[i]);
+			for (int i = strValues.length; i < values.length; i++)	
+				values[i] = getDefaultValue (type[i]);
+
+			// invoke the setter / set the field
+			invoke (match[0], match[1], values); 
+			return true;
+		}
+		catch (Throwable t)
+		{
+			throw new ParseException (obj + "." + entry.getKey () 
+				+ " = " + entry.getValue (), t);
+		}
+	} 
+
+
+	/**
+	 *	Removes leading and trailing single quotes from the given String, 
+	 *	if any.
+ 	 */
+	private static String trimQuote (String val)
+	{
+		if (val != null && val.startsWith ("'") && val.endsWith ("'"))
+			return val.substring (1, val.length () - 1);
+		return val;
+	}
+
+
+	/**
+	 *	Finds all the options that can be set on the provided class. This does
+	 *	not look for path-traversal expressions.
+	 *
+	 *	@param	type	The class for which available options should be listed.
+	 *	@return			The available option names in <code>type</code>. The
+	 * 					names will have initial caps. They will be ordered
+	 * 					alphabetically.
+	 */
+	public static Collection findOptionsFor (Class type)
+	{
+		Collection names = new TreeSet ();
+		// look for a setter method matching the key
+		Method[] meths = type.getMethods ();
+		Class[] params;
+		for (int i = 0; i < meths.length; i++)
+		{
+			if (meths[i].getName ().startsWith ("set"))
+			{
+				params = meths[i].getParameterTypes ();
+				if (params.length == 0)
+					continue;
+				if (params[0].isArray ())
+					continue;
+
+				names.add (StringUtils.capitalize (
+					meths[i].getName ().substring (3)));
+			}
+		}
+
+		// check for public fields
+		Field[] fields = type.getFields ();
+		for (int i = 0; i < fields.length; i++)
+			names.add (StringUtils.capitalize (fields[i].getName ()));
+
+		return names;
+	}
+
+
+	/**
+	 *	Matches a key to an object/setter pair.  
+	 *
+	 *	@param	key		the key given at the command line; may be of the form
+	 *					'foo.bar' to signify the 'bar' property of the
+	 *					'foo' owned object
+	 *	@param	match	an array of length 2, where the first index is set
+	 *					to the object to retrieve the setter for
+	 *	@return			true if a match was made, false otherwise; additionally,
+	 *					the first index of the match array will be set to
+	 *					the matching object and the second index will be
+	 *					set to the setter method or public field for the 
+	 *					property named by the key
+	 */
+	private static boolean matchOptionToMember (String key, Object[] match)
+		throws Exception
+	{
+		if (key == null || key.length () == 0)
+			return false;
+		
+		// unfortunately we can't use bean properties for setters; any
+		// setter with more than 1 arg is ignored; calc setter and getter 
+		// name to look for
+		String[] find = Strings.split (key, ".", 2);
+		String base = StringUtils.capitalise (find[0]);
+		String set = "set" + base;
+		String get = "get" + base;
+
+		// look for a setter/getter matching the key; look for methods first
+		Class type = match[0].getClass ();
+		Method[] meths = type.getMethods ();
+		Method setMeth = null;
+		Method getMeth = null;
+		Class[] params;
+		for (int i = 0; i < meths.length; i++)
+		{
+			if (meths[i].getName ().equals (set))
+			{
+				params = meths[i].getParameterTypes ();
+				if (params.length == 0)
+					continue;
+				if (params[0].isArray ())
+					continue;
+
+				// use this method if we haven't found any other setter, if
+				// it has less parameters than any other setter, or if it uses
+				// string parameters
+				if (setMeth == null) 
+					setMeth = meths[i];
+				else if (params.length < setMeth.getParameterTypes ().length)
+					setMeth = meths[i];
+				else if (params.length == setMeth.getParameterTypes ().length
+					&& params[0] == String.class)
+					setMeth = meths[i];
+			}
+			else if (meths[i].getName ().equals (get))
+				getMeth = meths[i];
+		}
+
+		// if no methods found, check for public field
+		Member setter = setMeth;
+		Member getter = getMeth;
+		if (setter == null)
+		{
+			Field[] fields = type.getFields ();
+			String uncapBase = StringUtils.uncapitalise (find[0]);
+			for (int i = 0; i < fields.length; i++)
+			{
+				if (fields[i].getName ().equals (base) 
+					|| fields[i].getName ().equals (uncapBase))
+				{
+					setter = fields[i];
+					getter = fields[i];
+					break;
+				}
+			}
+		}
+
+		// if no way to access property, give up
+		if (setter == null && getter == null)
+			return false;
+
+		// recurse on inner object with remainder of key?
+		if (find.length > 1)
+		{
+			Object inner = null;
+			if (getter != null)
+				inner = invoke (match[0], getter, null);
+
+			// if no getter or current inner is null, try to create a new 
+			// inner instance and set it in object
+			if (inner == null && setter != null)
+			{
+				Class innerType = getType (setter)[0];
+				inner = innerType.newInstance ();
+				invoke (match[0], setter, new Object[] { inner });
+			}
+			match[0] = inner;
+			return matchOptionToMember (find[1], match);
+		}
+
+		// got match; find setter for property
+		match[1] = setter;
+		return match[1] != null;
+	}
+
+
+	/**
+	 *	Return the types of the parameters needed to set the given member.
+	 */
+	private static Class[] getType (Object member)
+	{
+		if (member instanceof Method)
+			return ((Method) member).getParameterTypes ();
+		return new Class[] { ((Field) member).getType () };
+	}
+
+
+	/**
+	 *	Set the given member to the given value(s).
+	 */
+	private static Object invoke (Object target, Object member, Object[] values)
+		throws Exception
+	{
+		if (member instanceof Method)
+			return ((Method) member).invoke (target, values);
+		if (values == null || values.length == 0)
+			return ((Field) member).get (target);
+		((Field) member).set (target, values[0]);
+		return null;
+	}
+
+
+	/**
+	 *	Converts the given string into an object of the given type, or its
+	 *	wrapper type if it is primitive.
+	 */
+	private Object stringToObject (String str, Class type)
+		throws Exception
+	{
+		// special case for null and for strings
+		if (str == null || type == String.class)
+			return str;
+
+		// special case for creating Class instances
+		if (type == Class.class)
+			return Class.forName (str, false, getClass ().getClassLoader ());
+
+		// special case for numeric types that end in .0; strip the decimal
+		// places because it can kill int, short, long parsing
+		if (type.isPrimitive () || Number.class.isAssignableFrom (type))
+			if (str.length () > 2 && str.endsWith (".0"))
+				str = str.substring (0, str.length () - 2);
+		
+		// for primitives, recurse on wrapper type
+		if (type.isPrimitive ())
+			for (int i = 0; i < _primWrappers.length; i++)
+				if (type == _primWrappers[i][0])
+					return stringToObject (str, (Class) _primWrappers[i][1]);	
+
+		// look for a string constructor
+		Exception err = null;
+		try
+		{
+			Constructor cons = type.getConstructor 
+				(new Class[] { String.class });
+			if (type == Boolean.class && "t".equalsIgnoreCase (str))
+				str = "true";
+			return cons.newInstance (new Object[] { str });	
+		}
+		catch (Exception e)
+		{
+			err = e;
+		}
+
+		// special case: the arg value is a subtype name and a new instance
+		// of that type should be set as the object
+		Class subType = null;
+		try
+		{
+			subType = Class.forName (str);
+		}
+		catch (Exception e)
+		{
+			throw err;
+		}
+		if (!type.isAssignableFrom (subType))
+			throw err;
+		return subType.newInstance ();
+	}
+
+
+	/**
+	 *	Returns the default value for the given parameter type.
+	 */
+	private Object getDefaultValue (Class type)
+	{
+		for (int i = 0; i < _primWrappers.length; i++)
+			if (_primWrappers[i][0] == type)
+				return _primWrappers[i][2];
+		
+		return null;
+	}
+
+
+	/**
+	 *	Specialization of {@link #getBooleanProperty} to allow
+	 *	a value to appear under either of two keys; useful for short and
+	 *	long versions of command-line flags.
+	 */
+	public boolean getBooleanProperty (String key, String key2, boolean def)
+	{
+		String val = getProperty (key);
+		if (val == null)
+			val = getProperty (key2);
+		if (val == null)
+			return def;
+		return "t".equalsIgnoreCase (val) || "true".equalsIgnoreCase (val);
+	}
+
+
+	/**
+	 *	Specialization of {@link TypedProperties#getFloatProperty} to allow
+	 *	a value to appear under either of two keys; useful for short and
+	 *	long versions of command-line flags.
+	 */
+	public float getFloatProperty (String key, String key2, float def)
+	{
+		String val = getProperty (key);
+		if (val == null)
+			val = getProperty (key2);
+		return (val == null) ? def : Float.parseFloat (val);
+	}
+
+
+	/**
+	 *	Specialization of {@link TypedProperties#getDoubleProperty} to allow
+	 *	a value to appear under either of two keys; useful for short and
+	 *	long versions of command-line flags.
+	 */
+	public double getDoubleProperty (String key, String key2, double def)
+	{
+		String val = getProperty (key);
+		if (val == null)
+			val = getProperty (key2);
+		return (val == null) ? def : Double.parseDouble (val);
+	}
+
+
+	/**
+	 *	Specialization of {@link TypedProperties#getLongProperty} to allow
+	 *	a value to appear under either of two keys; useful for short and
+	 *	long versions of command-line flags.
+	 */
+	public long getLongProperty (String key, String key2, long def)
+	{
+		String val = getProperty (key);
+		if (val == null)
+			val = getProperty (key2);
+		return (val == null) ? def : Long.parseLong (val);
+	}
+
+
+	/**
+	 *	Specialization of {@link TypedProperties#getIntProperty} to allow
+	 *	a value to appear under either of two keys; useful for short and
+	 *	long versions of command-line flags.
+	 */
+	public int getIntProperty (String key, String key2, int def)
+	{
+		String val = getProperty (key);
+		if (val == null)
+			val = getProperty (key2);
+		return (val == null) ? def : Integer.parseInt (val);
+	}
+
+
+	/**
+	 *	Specialization of {@link Properties#getProperty} to allow
+	 *	a value to appear under either of two keys; useful for short and
+	 *	long versions of command-line flags.
+	 */
+	public String getProperty (String key, String key2, String def)
+	{
+		String val = getProperty (key);
+		return (val == null) ? getProperty (key2, def) : val;
+	}
+
+
+	/**
+	 *	Specialization of {@link TypedProperties#removeBooleanProperty} to allow
+	 *	a value to appear under either of two keys; useful for short and
+	 *	long versions of command-line flags.
+	 */
+	public boolean removeBooleanProperty (String key, String key2, boolean def)
+	{
+		String val = removeProperty (key);
+		if (val == null)
+			val = removeProperty (key2);
+		else
+			removeProperty (key2);
+		if (val == null)
+			return def;
+		return "t".equalsIgnoreCase (val) || "true".equalsIgnoreCase (val);
+	}
+
+
+	/**
+	 *	Specialization of {@link TypedProperties#removeFloatProperty} to allow
+	 *	a value to appear under either of two keys; useful for short and
+	 *	long versions of command-line flags.
+	 */
+	public float removeFloatProperty (String key, String key2, float def)
+	{
+		String val = removeProperty (key);
+		if (val == null)
+			val = removeProperty (key2);
+		else
+			removeProperty (key2);
+		return (val == null) ? def : Float.parseFloat (val);
+	}
+
+
+	/**
+	 *	Specialization of {@link TypedProperties#removeDoubleProperty} to allow
+	 *	a value to appear under either of two keys; useful for short and
+	 *	long versions of command-line flags.
+	 */
+	public double removeDoubleProperty (String key, String key2, double def)
+	{
+		String val = removeProperty (key);
+		if (val == null)
+			val = removeProperty (key2);
+		else
+			removeProperty (key2);
+		return (val == null) ? def : Double.parseDouble (val);
+	}
+
+
+	/**
+	 *	Specialization of {@link TypedProperties#removeLongProperty} to allow
+	 *	a value to appear under either of two keys; useful for short and
+	 *	long versions of command-line flags.
+	 */
+	public long removeLongProperty (String key, String key2, long def)
+	{
+		String val = removeProperty (key);
+		if (val == null)
+			val = removeProperty (key2);
+		else
+			removeProperty (key2);
+		return (val == null) ? def : Long.parseLong (val);
+	}
+
+
+	/**
+	 *	Specialization of {@link TypedProperties#removeIntProperty} to allow
+	 *	a value to appear under either of two keys; useful for short and
+	 *	long versions of command-line flags.
+	 */
+	public int removeIntProperty (String key, String key2, int def)
+	{
+		String val = removeProperty (key);
+		if (val == null)
+			val = removeProperty (key2);
+		else
+			removeProperty (key2);
+		return (val == null) ? def : Integer.parseInt (val);
+	}
+
+
+	/**
+	 *	Specialization of {@link Properties#removeProperty} to allow
+	 *	a value to appear under either of two keys; useful for short and
+	 *	long versions of command-line flags.
+	 */
+	public String removeProperty (String key, String key2, String def)
+	{
+		String val = removeProperty (key);
+		return (val == null) ? removeProperty (key2, def) : val;
+	}
+}

Propchange: incubator/openjpa/trunk/openjpa-lib/java/org/apache/openjpa/lib/util/Options.java
------------------------------------------------------------------------------
    svn:executable = *



Mime
View raw message