You are viewing a plain text version of this content. The canonical link for it is here.
Posted to commits@openjpa.apache.org by pc...@apache.org on 2006/05/14 05:26:02 UTC
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/...
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 <file-name>~. 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"->"12"</code><br />
+ * Resultant method call: <code>obj.setAge (12)</code></li>
+ * <li>Map Entry: <code>"range"->"1,20"</code><br />
+ * Resultant method call: <code>obj.setRange (1, 20)</code></li>
+ * <li>Map Entry: <code>"range"->"10"</code><br />
+ * Resultant method call: <code>obj.setRange (10, 10)</code></li>
+ * <li>Map Entry: <code>"brother.name"->"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 = *