You are viewing a plain text version of this content. The canonical link for it is here.
Posted to commits@struts.apache.org by lu...@apache.org on 2011/12/02 17:33:45 UTC
svn commit: r1209569 [26/50] - in /struts/struts2/branches/STRUTS_3_X:
apps/blank/src/main/java/example/ apps/blank/src/test/java/example/
apps/jboss-blank/src/main/java/example/
apps/jboss-blank/src/test/java/example/ apps/mailreader/src/main/java/mai...
Added: struts/struts2/branches/STRUTS_3_X/xwork-core/src/main/java/org/apache/struts2/xwork2/util/Element.java
URL: http://svn.apache.org/viewvc/struts/struts2/branches/STRUTS_3_X/xwork-core/src/main/java/org/apache/struts2/xwork2/util/Element.java?rev=1209569&view=auto
==============================================================================
--- struts/struts2/branches/STRUTS_3_X/xwork-core/src/main/java/org/apache/struts2/xwork2/util/Element.java (added)
+++ struts/struts2/branches/STRUTS_3_X/xwork-core/src/main/java/org/apache/struts2/xwork2/util/Element.java Fri Dec 2 16:33:03 2011
@@ -0,0 +1,81 @@
+/*
+ * Copyright 2002-2006,2009 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.struts2.xwork2.util;
+
+import java.lang.annotation.ElementType;
+import java.lang.annotation.Retention;
+import java.lang.annotation.RetentionPolicy;
+import java.lang.annotation.Target;
+
+/**
+ * <!-- START SNIPPET: description -->
+ * <p/>Sets the Element for type conversion.
+ * <!-- END SNIPPET: description -->
+ *
+ * <p/> <u>Annotation usage:</u>
+ *
+ * <!-- START SNIPPET: usage -->
+ * <p/>The Element annotation must be applied at field or method level.
+ * <!-- END SNIPPET: usage -->
+ * <p/> <u>Annotation parameters:</u>
+ *
+ * <!-- START SNIPPET: parameters -->
+ * <table>
+ * <thead>
+ * <tr>
+ * <th>Parameter</th>
+ * <th>Required</th>
+ * <th>Default</th>
+ * <th>Description</th>
+ * </tr>
+ * </thead>
+ * <tbody>
+ * <tr>
+ * <td>value</td>
+ * <td>no</td>
+ * <td>java.lang.Object.class</td>
+ * <td>The element property value.</td>
+ * </tr>
+ * </tbody>
+ * </table>
+ * <!-- END SNIPPET: parameters -->
+ *
+ * <p/> <u>Example code:</u>
+ * <pre>
+ * <!-- START SNIPPET: example -->
+ * // The key property for User objects within the users collection is the <code>userName</code> attribute.
+ * @Element( value = com.acme.User )
+ * private Map<Long, User> userMap;
+ *
+ * @Element( value = com.acme.User )
+ * public List<User> userList;
+ * <!-- END SNIPPET: example -->
+ * </pre>
+ *
+ * @author Rainer Hermanns
+ * @version $Id: Element.java 1209415 2011-12-02 11:24:48Z lukaszlenart $
+ */
+@Retention(RetentionPolicy.RUNTIME)
+@Target({ElementType.FIELD, ElementType.METHOD})
+public @interface Element {
+
+ /**
+ * The Element value.
+ * Defaults to <tt>java.lang.Object.class</tt>.
+ */
+ Class value() default java.lang.Object.class;
+}
Added: struts/struts2/branches/STRUTS_3_X/xwork-core/src/main/java/org/apache/struts2/xwork2/util/FileManager.java
URL: http://svn.apache.org/viewvc/struts/struts2/branches/STRUTS_3_X/xwork-core/src/main/java/org/apache/struts2/xwork2/util/FileManager.java?rev=1209569&view=auto
==============================================================================
--- struts/struts2/branches/STRUTS_3_X/xwork-core/src/main/java/org/apache/struts2/xwork2/util/FileManager.java (added)
+++ struts/struts2/branches/STRUTS_3_X/xwork-core/src/main/java/org/apache/struts2/xwork2/util/FileManager.java Fri Dec 2 16:33:03 2011
@@ -0,0 +1,324 @@
+/*
+ * Copyright 2002-2003,2009 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.struts2.xwork2.util;
+
+import org.apache.struts2.xwork2.util.logging.Logger;
+import org.apache.struts2.xwork2.util.logging.LoggerFactory;
+import org.apache.commons.io.FileUtils;
+
+import java.io.File;
+import java.io.IOException;
+import java.io.InputStream;
+import java.net.URISyntaxException;
+import java.net.URL;
+import java.util.Collections;
+import java.util.HashMap;
+import java.util.Map;
+import java.util.jar.JarFile;
+import java.util.zip.ZipEntry;
+
+/**
+ * FileManager
+ * <p/>
+ * This class was brought in from oscore trunk revision 147.
+ *
+ * @author Jason Carreira
+ * Created May 7, 2003 8:44:26 PM
+ */
+public class FileManager {
+ //~ Static fields/initializers /////////////////////////////////////////////
+
+ private static Logger LOG = LoggerFactory.getLogger(FileManager.class);
+
+ private static Map<String, Revision> files = Collections.synchronizedMap(new HashMap<String, Revision>());
+ protected static boolean reloadingConfigs = true;
+
+ private static final String JAR_FILE_NAME_SEPARATOR = "!/";
+ private static final String JAR_FILE_EXTENSION_END = ".jar/";
+
+
+ //~ Constructors ///////////////////////////////////////////////////////////
+
+ private FileManager() {
+ }
+
+ //~ Methods ////////////////////////////////////////////////////////////////
+
+ public static void setReloadingConfigs(boolean reloadingConfigs) {
+ FileManager.reloadingConfigs = reloadingConfigs;
+ }
+
+ public static boolean isReloadingConfigs() {
+ return reloadingConfigs;
+ }
+
+ public static boolean fileNeedsReloading(String fileName, Class clazz) {
+ URL fileUrl = ClassLoaderUtil.getResource(fileName, clazz);
+ return fileUrl != null && fileNeedsReloading(fileUrl.toString());
+ }
+
+ public static boolean fileNeedsReloading(String fileName) {
+ Revision revision = files.get(fileName);
+
+ if (revision == null) {
+ // no revision yet and we keep the revision history, so
+ // return whether the file needs to be loaded for the first time
+ return reloadingConfigs;
+ }
+
+ return revision.needsReloading();
+ }
+
+ /**
+ * Loads opens the named file and returns the InputStream
+ *
+ * @param fileName - the name of the file to open
+ * @return an InputStream of the file contents or null
+ * @throws IllegalArgumentException if there is no file with the given file name
+ */
+ public static InputStream loadFile(String fileName, Class clazz) {
+ URL fileUrl = ClassLoaderUtil.getResource(fileName, clazz);
+ return loadFile(fileUrl);
+ }
+
+ /**
+ * Loads opens the named file and returns the InputStream
+ *
+ * @param fileUrl - the URL of the file to open
+ * @return an InputStream of the file contents or null
+ * @throws IllegalArgumentException if there is no file with the given file name
+ */
+ public static InputStream loadFile(URL fileUrl) {
+ return loadFile(fileUrl, true);
+ }
+
+ /**
+ * Loads opens the named file and returns the InputStream
+ *
+ * @param fileUrl - the URL of the file to open
+ * @param openStream - if true, open an InputStream to the file and return it
+ * @return an InputStream of the file contents or null
+ * @throws IllegalArgumentException if there is no file with the given file name
+ */
+ public static InputStream loadFile(URL fileUrl, boolean openStream) {
+ if (fileUrl == null) {
+ return null;
+ }
+
+ String fileName = fileUrl.toString();
+ InputStream is = null;
+
+ if (openStream) {
+ try {
+ is = fileUrl.openStream();
+
+ if (is == null) {
+ throw new IllegalArgumentException("No file '" + fileName + "' found as a resource");
+ }
+ } catch (IOException e) {
+ throw new IllegalArgumentException("No file '" + fileName + "' found as a resource");
+ }
+ }
+
+ if (isReloadingConfigs()) {
+ Revision revision;
+
+ if (LOG.isDebugEnabled()) {
+ LOG.debug("Creating revision for URL: " +fileName);
+ }
+ if (URLUtil.isJBoss5Url(fileUrl)) {
+ revision = JBossFileRevision.build(fileUrl);
+ } else if (URLUtil.isJarURL(fileUrl)) {
+ revision = JarEntryRevision.build(fileUrl);
+ } else {
+ revision = FileRevision.build(fileUrl);
+ }
+ if (revision == null) {
+ files.put(fileName, Revision.build(fileUrl));
+ } else {
+ files.put(fileName, revision);
+ }
+ }
+ return is;
+ }
+
+ //~ Inner Classes //////////////////////////////////////////////////////////
+
+ /**
+ * Class represents common revsion resource, should be used as default class when no other option exists
+ */
+ private static class Revision {
+
+ public Revision() {
+ }
+
+ public boolean needsReloading() {
+ return false;
+ }
+
+ public static Revision build(URL fileUrl) {
+ return new Revision();
+ }
+ }
+
+ /**
+ * Represents file resource revision, used for file://* resources
+ */
+ private static class FileRevision extends Revision {
+ private File file;
+ private long lastModified;
+
+ public FileRevision(File file, long lastUpdated) {
+ if (file == null) {
+ throw new IllegalArgumentException("File cannot be null");
+ }
+
+ this.file = file;
+ this.lastModified = lastUpdated;
+ }
+
+ public File getFile() {
+ return file;
+ }
+
+ public void setLastModified(long lastModified) {
+ this.lastModified = lastModified;
+ }
+
+ public long getLastModified() {
+ return lastModified;
+ }
+
+ public boolean needsReloading() {
+ return this.lastModified < this.file.lastModified();
+ }
+
+ public static Revision build(URL fileUrl) {
+ File file;
+ try {
+ file = new File(fileUrl.toURI());
+ } catch (URISyntaxException e) {
+ file = new File(fileUrl.getPath());
+ } catch (Throwable t) {
+ return null;
+ }
+ if (file.exists() && file.canRead()) {
+ long lastModified = file.lastModified();
+ return new FileRevision(file, lastModified);
+ }
+ return null;
+ }
+ }
+
+ /**
+ * Represents file resource revision, used for vfszip://* resources
+ */
+ private static class JBossFileRevision extends FileRevision {
+
+ public JBossFileRevision(File file, long lastUpdated) {
+ super(file, lastUpdated);
+ }
+
+ public static Revision build(URL fileUrl) {
+ File file;
+ URL url = URLUtil.normalizeToFileProtocol(fileUrl);
+ try {
+ if (url != null) {
+ file = new File(url.toURI());
+ } else {
+ return null;
+ }
+ } catch (URISyntaxException e) {
+ file = new File(url.getPath());
+ }
+ if (file.exists() && file.canRead()) {
+ long lastModified = file.lastModified();
+ return new FileRevision(file, lastModified);
+ }
+ return null;
+ }
+ }
+
+ /**
+ * Represents jar resurce revision, used for jar://* resource
+ */
+ private static class JarEntryRevision extends Revision {
+
+ private String jarFileName;
+ private String fileNameInJar;
+ private long lastModified;
+
+ public JarEntryRevision(String jarFileName, String fileNameInJar, long lastModified) {
+ if ((jarFileName == null) || (fileNameInJar == null)) {
+ throw new IllegalArgumentException("JarFileName and FileNameInJar cannot be null");
+ }
+ this.jarFileName = jarFileName;
+ this.fileNameInJar = fileNameInJar;
+ this.lastModified = lastModified;
+ }
+
+ public boolean needsReloading() {
+ ZipEntry entry;
+ try {
+ JarFile jarFile = new JarFile(this.jarFileName);
+ entry = jarFile.getEntry(this.fileNameInJar);
+ }
+ catch (IOException e) {
+ entry = null;
+ }
+
+ return entry != null && (lastModified < entry.getTime());
+ }
+
+ public static Revision build(URL fileUrl) {
+ // File within a Jar
+ // Find separator index of jar filename and filename within jar
+ String jarFileName = "";
+ try {
+ String fileName = fileUrl.toString();
+ int separatorIndex = fileName.indexOf(JAR_FILE_NAME_SEPARATOR);
+ if (separatorIndex == -1) {
+ separatorIndex = fileName.lastIndexOf(JAR_FILE_EXTENSION_END);
+ }
+ if (separatorIndex == -1) {
+ if (LOG.isWarnEnabled()) {
+ LOG.warn("Could not find end of jar file!");
+ }
+ return null;
+ }
+ // Split file name
+ jarFileName = fileName.substring(0, separatorIndex);
+ int index = separatorIndex + JAR_FILE_NAME_SEPARATOR.length();
+ String fileNameInJar = fileName.substring(index).replaceAll("%20", " ");
+
+ URL url = URLUtil.normalizeToFileProtocol(fileUrl);
+ if (url != null) {
+ JarFile jarFile = new JarFile(FileUtils.toFile(url));
+ ZipEntry entry = jarFile.getEntry(fileNameInJar);
+ return new JarEntryRevision(jarFileName.toString(), fileNameInJar, entry.getTime());
+ } else {
+ return null;
+ }
+ } catch (Throwable e) {
+ if (LOG.isWarnEnabled()) {
+ LOG.warn("Could not create JarEntryRevision for [" + jarFileName + "]!", e);
+ }
+ return null;
+ }
+ }
+ }
+
+}
Added: struts/struts2/branches/STRUTS_3_X/xwork-core/src/main/java/org/apache/struts2/xwork2/util/Key.java
URL: http://svn.apache.org/viewvc/struts/struts2/branches/STRUTS_3_X/xwork-core/src/main/java/org/apache/struts2/xwork2/util/Key.java?rev=1209569&view=auto
==============================================================================
--- struts/struts2/branches/STRUTS_3_X/xwork-core/src/main/java/org/apache/struts2/xwork2/util/Key.java (added)
+++ struts/struts2/branches/STRUTS_3_X/xwork-core/src/main/java/org/apache/struts2/xwork2/util/Key.java Fri Dec 2 16:33:03 2011
@@ -0,0 +1,78 @@
+/*
+ * Copyright 2002-2006,2009 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.struts2.xwork2.util;
+
+import java.lang.annotation.ElementType;
+import java.lang.annotation.Retention;
+import java.lang.annotation.RetentionPolicy;
+import java.lang.annotation.Target;
+
+/**
+ * <!-- START SNIPPET: description -->
+ * <p/>Sets the Key for type conversion.
+ * <!-- END SNIPPET: description -->
+ *
+ * <p/> <u>Annotation usage:</u>
+ *
+ * <!-- START SNIPPET: usage -->
+ * <p/>The Key annotation must be applied at field or method level.
+ * <!-- END SNIPPET: usage -->
+ * <p/> <u>Annotation parameters:</u>
+ *
+ * <!-- START SNIPPET: parameters -->
+ * <table>
+ * <thead>
+ * <tr>
+ * <th>Parameter</th>
+ * <th>Required</th>
+ * <th>Default</th>
+ * <th>Description</th>
+ * </tr>
+ * </thead>
+ * <tbody>
+ * <tr>
+ * <td>value</td>
+ * <td>no</td>
+ * <td>java.lang.Object.class</td>
+ * <td>The key property value.</td>
+ * </tr>
+ * </tbody>
+ * </table>
+ * <!-- END SNIPPET: parameters -->
+ *
+ * <p/> <u>Example code:</u>
+ * <pre>
+ * <!-- START SNIPPET: example -->
+ * // The key property for User objects within the users collection is the <code>userName</code> attribute.
+ * @Key( value = java.lang.Long.class )
+ * private Map<Long, User> userMap;
+ * <!-- END SNIPPET: example -->
+ * </pre>
+ *
+ * @author Rainer Hermanns
+ * @version $Id: Key.java 1209415 2011-12-02 11:24:48Z lukaszlenart $
+ */
+@Retention(RetentionPolicy.RUNTIME)
+@Target({ElementType.FIELD, ElementType.METHOD})
+public @interface Key {
+
+ /**
+ * The Key value.
+ * Defaults to <tt>java.lang.Object.class</tt>.
+ */
+ Class value() default java.lang.Object.class;
+}
Added: struts/struts2/branches/STRUTS_3_X/xwork-core/src/main/java/org/apache/struts2/xwork2/util/KeyProperty.java
URL: http://svn.apache.org/viewvc/struts/struts2/branches/STRUTS_3_X/xwork-core/src/main/java/org/apache/struts2/xwork2/util/KeyProperty.java?rev=1209569&view=auto
==============================================================================
--- struts/struts2/branches/STRUTS_3_X/xwork-core/src/main/java/org/apache/struts2/xwork2/util/KeyProperty.java (added)
+++ struts/struts2/branches/STRUTS_3_X/xwork-core/src/main/java/org/apache/struts2/xwork2/util/KeyProperty.java Fri Dec 2 16:33:03 2011
@@ -0,0 +1,79 @@
+/*
+ * Copyright 2002-2006,2009 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.struts2.xwork2.util;
+
+import java.lang.annotation.ElementType;
+import java.lang.annotation.Retention;
+import java.lang.annotation.RetentionPolicy;
+import java.lang.annotation.Target;
+
+/**
+ * <!-- START SNIPPET: description -->
+ * <p/>Sets the KeyProperty for type conversion.
+ * <!-- END SNIPPET: description -->
+ *
+ * <p/> <u>Annotation usage:</u>
+ *
+ * <!-- START SNIPPET: usage -->
+ * <p/>The KeyProperty annotation must be applied at field or method level.
+ * <p/>This annotation should be used with Generic types, if the key property of the key element needs to be specified.
+ * <!-- END SNIPPET: usage -->
+ * <p/> <u>Annotation parameters:</u>
+ *
+ * <!-- START SNIPPET: parameters -->
+ * <table>
+ * <thead>
+ * <tr>
+ * <th>Parameter</th>
+ * <th>Required</th>
+ * <th>Default</th>
+ * <th>Description</th>
+ * </tr>
+ * </thead>
+ * <tbody>
+ * <tr>
+ * <td>value</td>
+ * <td>no</td>
+ * <td>id</td>
+ * <td>The key property value.</td>
+ * </tr>
+ * </tbody>
+ * </table>
+ * <!-- END SNIPPET: parameters -->
+ *
+ * <p/> <u>Example code:</u>
+ * <pre>
+ * <!-- START SNIPPET: example -->
+ * // The key property for User objects within the users collection is the <code>userName</code> attribute.
+ * @KeyProperty( value = "userName" )
+ * protected List<User> users = null;
+ * <!-- END SNIPPET: example -->
+ * </pre>
+ *
+ * @author Patrick Lightbody
+ * @author Rainer Hermanns
+ */
+@Retention(RetentionPolicy.RUNTIME)
+@Target({ElementType.FIELD, ElementType.METHOD})
+public @interface KeyProperty {
+
+ /**
+ * The KeyProperty value.
+ * Defaults to the <tt>id</tt> attribute.
+ */
+ String value() default "id";
+}
Added: struts/struts2/branches/STRUTS_3_X/xwork-core/src/main/java/org/apache/struts2/xwork2/util/LocalizedTextUtil.java
URL: http://svn.apache.org/viewvc/struts/struts2/branches/STRUTS_3_X/xwork-core/src/main/java/org/apache/struts2/xwork2/util/LocalizedTextUtil.java?rev=1209569&view=auto
==============================================================================
--- struts/struts2/branches/STRUTS_3_X/xwork-core/src/main/java/org/apache/struts2/xwork2/util/LocalizedTextUtil.java (added)
+++ struts/struts2/branches/STRUTS_3_X/xwork-core/src/main/java/org/apache/struts2/xwork2/util/LocalizedTextUtil.java Fri Dec 2 16:33:03 2011
@@ -0,0 +1,911 @@
+/*
+ * $Id: LocalizedTextUtil.java 1209415 2011-12-02 11:24:48Z lukaszlenart $
+ *
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements. See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership. The ASF licenses this file
+ * to you 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.struts2.xwork2.util;
+
+import org.apache.struts2.xwork2.ActionContext;
+import org.apache.struts2.xwork2.ActionInvocation;
+import org.apache.struts2.xwork2.ModelDriven;
+import org.apache.struts2.xwork2.conversion.impl.XWorkConverter;
+import org.apache.struts2.xwork2.util.logging.Logger;
+import org.apache.struts2.xwork2.util.logging.LoggerFactory;
+import org.apache.struts2.xwork2.util.reflection.ReflectionProviderFactory;
+import org.apache.commons.lang.ObjectUtils;
+
+import java.beans.PropertyDescriptor;
+import java.lang.reflect.Field;
+import java.lang.reflect.InvocationTargetException;
+import java.lang.reflect.Method;
+import java.text.MessageFormat;
+import java.util.*;
+import java.util.concurrent.ConcurrentHashMap;
+import java.util.concurrent.ConcurrentMap;
+import java.util.concurrent.CopyOnWriteArrayList;
+
+
+/**
+ * Provides support for localization in XWork.
+ * <p/>
+ * <!-- START SNIPPET: searchorder -->
+ * Resource bundles are searched in the following order:<p/>
+ * <p/>
+ * <ol>
+ * <li>ActionClass.properties</li>
+ * <li>Interface.properties (every interface and sub-interface)</li>
+ * <li>BaseClass.properties (all the way to Object.properties)</li>
+ * <li>ModelDriven's model (if implements ModelDriven), for the model object repeat from 1</li>
+ * <li>package.properties (of the directory where class is located and every parent directory all the way to the root directory)</li>
+ * <li>search up the i18n message key hierarchy itself</li>
+ * <li>global resource properties</li>
+ * </ol>
+ * <p/>
+ * <!-- END SNIPPET: searchorder -->
+ * <p/>
+ * <!-- START SNIPPET: packagenote -->
+ * To clarify #5, while traversing the package hierarchy, Struts 2 will look for a file package.properties:<p/>
+ * com/<br/>
+ * acme/<br/>
+ * package.properties<br/>
+ * actions/<br/>
+ * package.properties<br/>
+ * FooAction.java<br/>
+ * FooAction.properties<br/>
+ * <p/>
+ * If FooAction.properties does not exist, com/acme/action/package.properties will be searched for, if
+ * not found com/acme/package.properties, if not found com/package.properties, etc.
+ * <p/>
+ * <!-- END SNIPPET: packagenote -->
+ * <p/>
+ * <!-- START SNIPPET: globalresource -->
+ * A global resource bundle could be specified programatically, as well as the locale.
+ * <p/>
+ * <!-- END SNIPPET: globalresource -->
+ *
+ * @author Jason Carreira
+ * @author Mark Woon
+ * @author Rainer Hermanns
+ * @author tm_jee
+ * @version $Date: 2011-12-02 12:24:48 +0100 (Fri, 02 Dec 2011) $ $Id: LocalizedTextUtil.java 1209415 2011-12-02 11:24:48Z lukaszlenart $
+ */
+public class LocalizedTextUtil {
+
+ private static final List<String> DEFAULT_RESOURCE_BUNDLES = new CopyOnWriteArrayList<String>();
+ private static final Logger LOG = LoggerFactory.getLogger(LocalizedTextUtil.class);
+ private static boolean reloadBundles = false;
+ private static final ResourceBundle EMPTY_BUNDLE = new EmptyResourceBundle();
+ private static final ConcurrentMap<String, ResourceBundle> bundlesMap = new ConcurrentHashMap<String, ResourceBundle>();
+ private static final ConcurrentMap<MessageFormatKey, MessageFormat> messageFormats = new ConcurrentHashMap<MessageFormatKey, MessageFormat>();
+
+ private static ClassLoader delegatedClassLoader;
+ private static final String RELOADED = "org.apache.struts2.xwork2.util.LocalizedTextUtil.reloaded";
+
+ static {
+ clearDefaultResourceBundles();
+ }
+
+
+ /**
+ * Clears the internal list of resource bundles.
+ */
+ public static void clearDefaultResourceBundles() {
+ if (DEFAULT_RESOURCE_BUNDLES != null) {
+ synchronized (DEFAULT_RESOURCE_BUNDLES) {
+ DEFAULT_RESOURCE_BUNDLES.clear();
+ DEFAULT_RESOURCE_BUNDLES.add("org/apache/struts2/xwork2/xwork-messages");
+ }
+ } else {
+ synchronized (DEFAULT_RESOURCE_BUNDLES) {
+ DEFAULT_RESOURCE_BUNDLES.add("org/apache/struts2/xwork2/xwork-messages");
+ }
+ }
+ }
+
+ /**
+ * Should resorce bundles be reloaded.
+ *
+ * @param reloadBundles reload bundles?
+ */
+ public static void setReloadBundles(boolean reloadBundles) {
+ LocalizedTextUtil.reloadBundles = reloadBundles;
+ }
+
+ /**
+ * Add's the bundle to the internal list of default bundles.
+ * <p/>
+ * If the bundle already exists in the list it will be readded.
+ *
+ * @param resourceBundleName the name of the bundle to add.
+ */
+ public static void addDefaultResourceBundle(String resourceBundleName) {
+ //make sure this doesn't get added more than once
+ synchronized (DEFAULT_RESOURCE_BUNDLES) {
+ DEFAULT_RESOURCE_BUNDLES.remove(resourceBundleName);
+ DEFAULT_RESOURCE_BUNDLES.add(0, resourceBundleName);
+ }
+
+ if (LOG.isDebugEnabled()) {
+ LOG.debug("Added default resource bundle '" + resourceBundleName + "' to default resource bundles = " + DEFAULT_RESOURCE_BUNDLES);
+ }
+ }
+
+ /**
+ * Builds a {@link java.util.Locale} from a String of the form en_US_foo into a Locale
+ * with language "en", country "US" and variant "foo". This will parse the output of
+ * {@link java.util.Locale#toString()}.
+ *
+ * @param localeStr The locale String to parse.
+ * @param defaultLocale The locale to use if localeStr is <tt>null</tt>.
+ * @return requested Locale
+ */
+ public static Locale localeFromString(String localeStr, Locale defaultLocale) {
+ if ((localeStr == null) || (localeStr.trim().length() == 0) || ("_".equals(localeStr))) {
+ if (defaultLocale != null) {
+ return defaultLocale;
+ }
+ return Locale.getDefault();
+ }
+
+ int index = localeStr.indexOf('_');
+ if (index < 0) {
+ return new Locale(localeStr);
+ }
+
+ String language = localeStr.substring(0, index);
+ if (index == localeStr.length()) {
+ return new Locale(language);
+ }
+
+ localeStr = localeStr.substring(index + 1);
+ index = localeStr.indexOf('_');
+ if (index < 0) {
+ return new Locale(language, localeStr);
+ }
+
+ String country = localeStr.substring(0, index);
+ if (index == localeStr.length()) {
+ return new Locale(language, country);
+ }
+
+ localeStr = localeStr.substring(index + 1);
+ return new Locale(language, country, localeStr);
+ }
+
+ /**
+ * Returns a localized message for the specified key, aTextName. Neither the key nor the
+ * message is evaluated.
+ *
+ * @param aTextName the message key
+ * @param locale the locale the message should be for
+ * @return a localized message based on the specified key, or null if no localized message can be found for it
+ */
+ public static String findDefaultText(String aTextName, Locale locale) {
+ List<String> localList = DEFAULT_RESOURCE_BUNDLES;
+
+ for (String bundleName : localList) {
+ ResourceBundle bundle = findResourceBundle(bundleName, locale);
+ if (bundle != null) {
+ reloadBundles();
+ try {
+ return bundle.getString(aTextName);
+ } catch (MissingResourceException e) {
+ // ignore and try others
+ }
+ }
+ }
+
+ return null;
+ }
+
+ /**
+ * Returns a localized message for the specified key, aTextName, substituting variables from the
+ * array of params into the message. Neither the key nor the message is evaluated.
+ *
+ * @param aTextName the message key
+ * @param locale the locale the message should be for
+ * @param params an array of objects to be substituted into the message text
+ * @return A formatted message based on the specified key, or null if no localized message can be found for it
+ */
+ public static String findDefaultText(String aTextName, Locale locale, Object[] params) {
+ String defaultText = findDefaultText(aTextName, locale);
+ if (defaultText != null) {
+ MessageFormat mf = buildMessageFormat(defaultText, locale);
+ return formatWithNullDetection(mf, params);
+ }
+ return null;
+ }
+
+ /**
+ * Finds the given resorce bundle by it's name.
+ * <p/>
+ * Will use <code>Thread.currentThread().getContextClassLoader()</code> as the classloader.
+ * If {@link #delegatedClassLoader} is defined and the bundle cannot be found the current
+ * classloader it will delegate to that.
+ *
+ * @param aBundleName the name of the bundle (usually it's FQN classname).
+ * @param locale the locale.
+ * @return the bundle, <tt>null</tt> if not found.
+ */
+ public static ResourceBundle findResourceBundle(String aBundleName, Locale locale) {
+ String key = createMissesKey(aBundleName, locale);
+
+ ResourceBundle bundle;
+
+ try {
+ if (!bundlesMap.containsKey(key)) {
+ bundle = ResourceBundle.getBundle(aBundleName, locale, Thread.currentThread().getContextClassLoader());
+ bundlesMap.put(key, bundle);
+ }
+
+ bundle = bundlesMap.get(key);
+ } catch (MissingResourceException ex) {
+ if (delegatedClassLoader != null) {
+ try {
+ if (!bundlesMap.containsKey(key)) {
+ bundle = ResourceBundle.getBundle(aBundleName, locale, delegatedClassLoader);
+ bundlesMap.put(key, bundle);
+ }
+
+ bundle = bundlesMap.get(key);
+
+ } catch (MissingResourceException e) {
+ bundle = EMPTY_BUNDLE;
+ bundlesMap.put(key, bundle);
+ }
+ } else {
+ bundle = EMPTY_BUNDLE;
+ bundlesMap.put(key, bundle);
+ }
+ }
+ return (bundle == EMPTY_BUNDLE) ? null : bundle;
+ }
+
+ /**
+ * Sets a {@link ClassLoader} to look up the bundle from if none can be found on the current thread's classloader
+ *
+ * @param classLoader
+ */
+ public static void setDelegatedClassLoader(final ClassLoader classLoader) {
+ synchronized (bundlesMap) {
+ delegatedClassLoader = classLoader;
+ }
+ }
+
+ /**
+ * Removes the bundle from any cached "misses"
+ *
+ * @param bundleName
+ */
+ public static void clearBundle(final String bundleName) {
+ bundlesMap.remove(bundleName);
+ }
+
+
+ /**
+ * Creates a key to used for lookup/storing in the bundle misses cache.
+ *
+ * @param aBundleName the name of the bundle (usually it's FQN classname).
+ * @param locale the locale.
+ * @return the key to use for lookup/storing in the bundle misses cache.
+ */
+ private static String createMissesKey(String aBundleName, Locale locale) {
+ return aBundleName + "_" + locale.toString();
+ }
+
+ /**
+ * Calls {@link #findText(Class aClass, String aTextName, Locale locale, String defaultMessage, Object[] args)}
+ * with aTextName as the default message.
+ *
+ * @see #findText(Class aClass, String aTextName, Locale locale, String defaultMessage, Object[] args)
+ */
+ public static String findText(Class aClass, String aTextName, Locale locale) {
+ return findText(aClass, aTextName, locale, aTextName, new Object[0]);
+ }
+
+ /**
+ * Finds a localized text message for the given key, aTextName. Both the key and the message
+ * itself is evaluated as required. The following algorithm is used to find the requested
+ * message:
+ * <p/>
+ * <ol>
+ * <li>Look for message in aClass' class hierarchy.
+ * <ol>
+ * <li>Look for the message in a resource bundle for aClass</li>
+ * <li>If not found, look for the message in a resource bundle for any implemented interface</li>
+ * <li>If not found, traverse up the Class' hierarchy and repeat from the first sub-step</li>
+ * </ol></li>
+ * <li>If not found and aClass is a {@link org.apache.struts2.xwork2.ModelDriven} Action, then look for message in
+ * the model's class hierarchy (repeat sub-steps listed above).</li>
+ * <li>If not found, look for message in child property. This is determined by evaluating
+ * the message key as an OGNL expression. For example, if the key is
+ * <i>user.address.state</i>, then it will attempt to see if "user" can be resolved into an
+ * object. If so, repeat the entire process fromthe beginning with the object's class as
+ * aClass and "address.state" as the message key.</li>
+ * <li>If not found, look for the message in aClass' package hierarchy.</li>
+ * <li>If still not found, look for the message in the default resource bundles.</li>
+ * <li>Return defaultMessage</li>
+ * </ol>
+ * <p/>
+ * When looking for the message, if the key indexes a collection (e.g. user.phone[0]) and a
+ * message for that specific key cannot be found, the general form will also be looked up
+ * (i.e. user.phone[*]).
+ * <p/>
+ * If a message is found, it will also be interpolated. Anything within <code>${...}</code>
+ * will be treated as an OGNL expression and evaluated as such.
+ *
+ * @param aClass the class whose name to use as the start point for the search
+ * @param aTextName the key to find the text message for
+ * @param locale the locale the message should be for
+ * @param defaultMessage the message to be returned if no text message can be found in any
+ * resource bundle
+ * @return the localized text, or null if none can be found and no defaultMessage is provided
+ */
+ public static String findText(Class aClass, String aTextName, Locale locale, String defaultMessage, Object[] args) {
+ ValueStack valueStack = ActionContext.getContext().getValueStack();
+ return findText(aClass, aTextName, locale, defaultMessage, args, valueStack);
+
+ }
+
+ /**
+ * Finds a localized text message for the given key, aTextName. Both the key and the message
+ * itself is evaluated as required. The following algorithm is used to find the requested
+ * message:
+ * <p/>
+ * <ol>
+ * <li>Look for message in aClass' class hierarchy.
+ * <ol>
+ * <li>Look for the message in a resource bundle for aClass</li>
+ * <li>If not found, look for the message in a resource bundle for any implemented interface</li>
+ * <li>If not found, traverse up the Class' hierarchy and repeat from the first sub-step</li>
+ * </ol></li>
+ * <li>If not found and aClass is a {@link org.apache.struts2.xwork2.ModelDriven} Action, then look for message in
+ * the model's class hierarchy (repeat sub-steps listed above).</li>
+ * <li>If not found, look for message in child property. This is determined by evaluating
+ * the message key as an OGNL expression. For example, if the key is
+ * <i>user.address.state</i>, then it will attempt to see if "user" can be resolved into an
+ * object. If so, repeat the entire process fromthe beginning with the object's class as
+ * aClass and "address.state" as the message key.</li>
+ * <li>If not found, look for the message in aClass' package hierarchy.</li>
+ * <li>If still not found, look for the message in the default resource bundles.</li>
+ * <li>Return defaultMessage</li>
+ * </ol>
+ * <p/>
+ * When looking for the message, if the key indexes a collection (e.g. user.phone[0]) and a
+ * message for that specific key cannot be found, the general form will also be looked up
+ * (i.e. user.phone[*]).
+ * <p/>
+ * If a message is found, it will also be interpolated. Anything within <code>${...}</code>
+ * will be treated as an OGNL expression and evaluated as such.
+ * <p/>
+ * If a message is <b>not</b> found a WARN log will be logged.
+ *
+ * @param aClass the class whose name to use as the start point for the search
+ * @param aTextName the key to find the text message for
+ * @param locale the locale the message should be for
+ * @param defaultMessage the message to be returned if no text message can be found in any
+ * resource bundle
+ * @param valueStack the value stack to use to evaluate expressions instead of the
+ * one in the ActionContext ThreadLocal
+ * @return the localized text, or null if none can be found and no defaultMessage is provided
+ */
+ public static String findText(Class aClass, String aTextName, Locale locale, String defaultMessage, Object[] args,
+ ValueStack valueStack) {
+ String indexedTextName = null;
+ if (aTextName == null) {
+ if (LOG.isWarnEnabled()) {
+ LOG.warn("Trying to find text with null key!");
+ }
+ aTextName = "";
+ }
+ // calculate indexedTextName (collection[*]) if applicable
+ if (aTextName.contains("[")) {
+ int i = -1;
+
+ indexedTextName = aTextName;
+
+ while ((i = indexedTextName.indexOf("[", i + 1)) != -1) {
+ int j = indexedTextName.indexOf("]", i);
+ String a = indexedTextName.substring(0, i);
+ String b = indexedTextName.substring(j);
+ indexedTextName = a + "[*" + b;
+ }
+ }
+
+ // search up class hierarchy
+ String msg = findMessage(aClass, aTextName, indexedTextName, locale, args, null, valueStack);
+
+ if (msg != null) {
+ return msg;
+ }
+
+ if (ModelDriven.class.isAssignableFrom(aClass)) {
+ ActionContext context = ActionContext.getContext();
+ // search up model's class hierarchy
+ ActionInvocation actionInvocation = context.getActionInvocation();
+
+ // ActionInvocation may be null if we're being run from a Sitemesh filter, so we won't get model texts if this is null
+ if (actionInvocation != null) {
+ Object action = actionInvocation.getAction();
+ if (action instanceof ModelDriven) {
+ Object model = ((ModelDriven) action).getModel();
+ if (model != null) {
+ msg = findMessage(model.getClass(), aTextName, indexedTextName, locale, args, null, valueStack);
+ if (msg != null) {
+ return msg;
+ }
+ }
+ }
+ }
+ }
+
+ // nothing still? alright, search the package hierarchy now
+ for (Class clazz = aClass;
+ (clazz != null) && !clazz.equals(Object.class);
+ clazz = clazz.getSuperclass()) {
+
+ String basePackageName = clazz.getName();
+ while (basePackageName.lastIndexOf('.') != -1) {
+ basePackageName = basePackageName.substring(0, basePackageName.lastIndexOf('.'));
+ String packageName = basePackageName + ".package";
+ msg = getMessage(packageName, locale, aTextName, valueStack, args);
+
+ if (msg != null) {
+ return msg;
+ }
+
+ if (indexedTextName != null) {
+ msg = getMessage(packageName, locale, indexedTextName, valueStack, args);
+
+ if (msg != null) {
+ return msg;
+ }
+ }
+ }
+ }
+
+ // see if it's a child property
+ int idx = aTextName.indexOf(".");
+
+ if (idx != -1) {
+ String newKey = null;
+ String prop = null;
+
+ if (aTextName.startsWith(XWorkConverter.CONVERSION_ERROR_PROPERTY_PREFIX)) {
+ idx = aTextName.indexOf(".", XWorkConverter.CONVERSION_ERROR_PROPERTY_PREFIX.length());
+
+ if (idx != -1) {
+ prop = aTextName.substring(XWorkConverter.CONVERSION_ERROR_PROPERTY_PREFIX.length(), idx);
+ newKey = XWorkConverter.CONVERSION_ERROR_PROPERTY_PREFIX + aTextName.substring(idx + 1);
+ }
+ } else {
+ prop = aTextName.substring(0, idx);
+ newKey = aTextName.substring(idx + 1);
+ }
+
+ if (prop != null) {
+ Object obj = valueStack.findValue(prop);
+ try {
+ Object actionObj = ReflectionProviderFactory.getInstance().getRealTarget(prop, valueStack.getContext(), valueStack.getRoot());
+ if (actionObj != null) {
+ PropertyDescriptor propertyDescriptor = ReflectionProviderFactory.getInstance().getPropertyDescriptor(actionObj.getClass(), prop);
+
+ if (propertyDescriptor != null) {
+ Class clazz = propertyDescriptor.getPropertyType();
+
+ if (clazz != null) {
+ if (obj != null)
+ valueStack.push(obj);
+ msg = findText(clazz, newKey, locale, null, args);
+ if (obj != null)
+ valueStack.pop();
+
+ if (msg != null) {
+ return msg;
+ }
+ }
+ }
+ }
+ } catch (Exception e) {
+ LOG.debug("unable to find property " + prop, e);
+ }
+ }
+ }
+
+ // get default
+ GetDefaultMessageReturnArg result = null;
+ if (indexedTextName == null) {
+ result = getDefaultMessage(aTextName, locale, valueStack, args, defaultMessage);
+ } else {
+ result = getDefaultMessage(aTextName, locale, valueStack, args, null);
+ if (result != null && result.message != null) {
+ return result.message;
+ }
+ result = getDefaultMessage(indexedTextName, locale, valueStack, args, defaultMessage);
+ }
+
+ // could we find the text, if not log a warn
+ if (unableToFindTextForKey(result) && LOG.isDebugEnabled()) {
+ String warn = "Unable to find text for key '" + aTextName + "' ";
+ if (indexedTextName != null) {
+ warn += " or indexed key '" + indexedTextName + "' ";
+ }
+ warn += "in class '" + aClass.getName() + "' and locale '" + locale + "'";
+ LOG.debug(warn);
+ }
+
+ return result != null ? result.message : null;
+ }
+
+ /**
+ * Determines if we found the text in the bundles.
+ *
+ * @param result the result so far
+ * @return <tt>true</tt> if we could <b>not</b> find the text, <tt>false</tt> if the text was found (=success).
+ */
+ private static boolean unableToFindTextForKey(GetDefaultMessageReturnArg result) {
+ if (result == null || result.message == null) {
+ return true;
+ }
+
+ // did we find it in the bundle, then no problem?
+ if (result.foundInBundle) {
+ return false;
+ }
+
+ // not found in bundle
+ return true;
+ }
+
+ /**
+ * Finds a localized text message for the given key, aTextName, in the specified resource bundle
+ * with aTextName as the default message.
+ * <p/>
+ * If a message is found, it will also be interpolated. Anything within <code>${...}</code>
+ * will be treated as an OGNL expression and evaluated as such.
+ *
+ * @see #findText(java.util.ResourceBundle, String, java.util.Locale, String, Object[])
+ */
+ public static String findText(ResourceBundle bundle, String aTextName, Locale locale) {
+ return findText(bundle, aTextName, locale, aTextName, new Object[0]);
+ }
+
+ /**
+ * Finds a localized text message for the given key, aTextName, in the specified resource
+ * bundle.
+ * <p/>
+ * If a message is found, it will also be interpolated. Anything within <code>${...}</code>
+ * will be treated as an OGNL expression and evaluated as such.
+ * <p/>
+ * If a message is <b>not</b> found a WARN log will be logged.
+ *
+ * @param bundle the bundle
+ * @param aTextName the key
+ * @param locale the locale
+ * @param defaultMessage the default message to use if no message was found in the bundle
+ * @param args arguments for the message formatter.
+ */
+ public static String findText(ResourceBundle bundle, String aTextName, Locale locale, String defaultMessage, Object[] args) {
+ ValueStack valueStack = ActionContext.getContext().getValueStack();
+ return findText(bundle, aTextName, locale, defaultMessage, args, valueStack);
+ }
+
+ /**
+ * Finds a localized text message for the given key, aTextName, in the specified resource
+ * bundle.
+ * <p/>
+ * If a message is found, it will also be interpolated. Anything within <code>${...}</code>
+ * will be treated as an OGNL expression and evaluated as such.
+ * <p/>
+ * If a message is <b>not</b> found a WARN log will be logged.
+ *
+ * @param bundle the bundle
+ * @param aTextName the key
+ * @param locale the locale
+ * @param defaultMessage the default message to use if no message was found in the bundle
+ * @param args arguments for the message formatter.
+ * @param valueStack the OGNL value stack.
+ */
+ public static String findText(ResourceBundle bundle, String aTextName, Locale locale, String defaultMessage, Object[] args,
+ ValueStack valueStack) {
+ try {
+ reloadBundles(valueStack.getContext());
+
+ String message = TextParseUtil.translateVariables(bundle.getString(aTextName), valueStack);
+ MessageFormat mf = buildMessageFormat(message, locale);
+
+ return formatWithNullDetection(mf, args);
+ } catch (MissingResourceException ex) {
+ // ignore
+ }
+
+ GetDefaultMessageReturnArg result = getDefaultMessage(aTextName, locale, valueStack, args, defaultMessage);
+ if (LOG.isWarnEnabled() && unableToFindTextForKey(result)) {
+ LOG.warn("Unable to find text for key '" + aTextName + "' in ResourceBundles for locale '" + locale + "'");
+ }
+ return result != null ? result.message : null;
+ }
+
+ /**
+ * Gets the default message.
+ */
+ private static GetDefaultMessageReturnArg getDefaultMessage(String key, Locale locale, ValueStack valueStack, Object[] args,
+ String defaultMessage) {
+ GetDefaultMessageReturnArg result = null;
+ boolean found = true;
+
+ if (key != null) {
+ String message = findDefaultText(key, locale);
+
+ if (message == null) {
+ message = defaultMessage;
+ found = false; // not found in bundles
+ }
+
+ // defaultMessage may be null
+ if (message != null) {
+ MessageFormat mf = buildMessageFormat(TextParseUtil.translateVariables(message, valueStack), locale);
+
+ String msg = formatWithNullDetection(mf, args);
+ result = new GetDefaultMessageReturnArg(msg, found);
+ }
+ }
+
+ return result;
+ }
+
+ /**
+ * Gets the message from the named resource bundle.
+ */
+ private static String getMessage(String bundleName, Locale locale, String key, ValueStack valueStack, Object[] args) {
+ ResourceBundle bundle = findResourceBundle(bundleName, locale);
+ if (bundle == null) {
+ return null;
+ }
+ reloadBundles(valueStack.getContext());
+ try {
+ String message = TextParseUtil.translateVariables(bundle.getString(key), valueStack);
+ MessageFormat mf = buildMessageFormat(message, locale);
+ return formatWithNullDetection(mf, args);
+ } catch (MissingResourceException e) {
+ return null;
+ }
+ }
+
+ private static String formatWithNullDetection(MessageFormat mf, Object[] args) {
+ String message = mf.format(args);
+ if ("null".equals(message)) {
+ return null;
+ } else {
+ return message;
+ }
+ }
+
+ private static MessageFormat buildMessageFormat(String pattern, Locale locale) {
+ MessageFormatKey key = new MessageFormatKey(pattern, locale);
+ MessageFormat format = messageFormats.get(key);
+ if (format == null) {
+ format = new MessageFormat(pattern);
+ format.setLocale(locale);
+ format.applyPattern(pattern);
+ messageFormats.put(key, format);
+ }
+
+ return format;
+ }
+
+ /**
+ * Traverse up class hierarchy looking for message. Looks at class, then implemented interface,
+ * before going up hierarchy.
+ */
+ private static String findMessage(Class clazz, String key, String indexedKey, Locale locale, Object[] args, Set<String> checked,
+ ValueStack valueStack) {
+ if (checked == null) {
+ checked = new TreeSet<String>();
+ } else if (checked.contains(clazz.getName())) {
+ return null;
+ }
+
+ // look in properties of this class
+ String msg = getMessage(clazz.getName(), locale, key, valueStack, args);
+
+ if (msg != null) {
+ return msg;
+ }
+
+ if (indexedKey != null) {
+ msg = getMessage(clazz.getName(), locale, indexedKey, valueStack, args);
+
+ if (msg != null) {
+ return msg;
+ }
+ }
+
+ // look in properties of implemented interfaces
+ Class[] interfaces = clazz.getInterfaces();
+
+ for (Class anInterface : interfaces) {
+ msg = getMessage(anInterface.getName(), locale, key, valueStack, args);
+
+ if (msg != null) {
+ return msg;
+ }
+
+ if (indexedKey != null) {
+ msg = getMessage(anInterface.getName(), locale, indexedKey, valueStack, args);
+
+ if (msg != null) {
+ return msg;
+ }
+ }
+ }
+
+ // traverse up hierarchy
+ if (clazz.isInterface()) {
+ interfaces = clazz.getInterfaces();
+
+ for (Class anInterface : interfaces) {
+ msg = findMessage(anInterface, key, indexedKey, locale, args, checked, valueStack);
+
+ if (msg != null) {
+ return msg;
+ }
+ }
+ } else {
+ if (!clazz.equals(Object.class) && !clazz.isPrimitive()) {
+ return findMessage(clazz.getSuperclass(), key, indexedKey, locale, args, checked, valueStack);
+ }
+ }
+
+ return null;
+ }
+
+ private static void reloadBundles() {
+ reloadBundles(ActionContext.getContext() != null ? ActionContext.getContext().getContextMap() : null);
+ }
+
+ private static void reloadBundles(Map<String, Object> context) {
+ if (reloadBundles) {
+ try {
+ Boolean reloaded;
+ if (context != null) {
+ reloaded = (Boolean) ObjectUtils.defaultIfNull(context.get(RELOADED), Boolean.FALSE);
+ }else {
+ reloaded = Boolean.FALSE;
+ }
+ if (!reloaded) {
+ bundlesMap.clear();
+ clearMap(ResourceBundle.class, null, "cacheList");
+ // now, for the true and utter hack, if we're running in tomcat, clear
+ // it's class loader resource cache as well.
+ clearTomcatCache();
+ if(context!=null)
+ context.put(RELOADED, true);
+ if (LOG.isDebugEnabled()) {
+ LOG.debug("Resource bundles reloaded");
+ }
+ }
+ } catch (Exception e) {
+ LOG.error("Could not reload resource bundles", e);
+ }
+ }
+ }
+
+
+ private static void clearTomcatCache() {
+ ClassLoader loader = Thread.currentThread().getContextClassLoader();
+ // no need for compilation here.
+ Class cl = loader.getClass();
+
+ try {
+ if ("org.apache.catalina.loader.WebappClassLoader".equals(cl.getName())) {
+ clearMap(cl, loader, "resourceEntries");
+ } else {
+ if (LOG.isDebugEnabled()) {
+ LOG.debug("class loader " + cl.getName() + " is not tomcat loader.");
+ }
+ }
+ } catch (Exception e) {
+ if (LOG.isWarnEnabled()) {
+ LOG.warn("couldn't clear tomcat cache", e);
+ }
+ }
+ }
+
+
+ private static void clearMap(Class cl, Object obj, String name)
+ throws NoSuchFieldException, IllegalAccessException, NoSuchMethodException, InvocationTargetException {
+
+ Field field = cl.getDeclaredField(name);
+ field.setAccessible(true);
+
+ Object cache = field.get(obj);
+
+ synchronized (cache) {
+ Class ccl = cache.getClass();
+ Method clearMethod = ccl.getMethod("clear");
+ clearMethod.invoke(cache);
+ }
+ }
+
+ /**
+ * Clears all the internal lists.
+ */
+ public static void reset() {
+ clearDefaultResourceBundles();
+ bundlesMap.clear();
+ messageFormats.clear();
+ }
+
+ static class MessageFormatKey {
+ String pattern;
+ Locale locale;
+
+ MessageFormatKey(String pattern, Locale locale) {
+ this.pattern = pattern;
+ this.locale = locale;
+ }
+
+ @Override
+ public boolean equals(Object o) {
+ if (this == o) return true;
+ if (!(o instanceof MessageFormatKey)) return false;
+
+ final MessageFormatKey messageFormatKey = (MessageFormatKey) o;
+
+ if (locale != null ? !locale.equals(messageFormatKey.locale) : messageFormatKey.locale != null)
+ return false;
+ if (pattern != null ? !pattern.equals(messageFormatKey.pattern) : messageFormatKey.pattern != null)
+ return false;
+
+ return true;
+ }
+
+ @Override
+ public int hashCode() {
+ int result;
+ result = (pattern != null ? pattern.hashCode() : 0);
+ result = 29 * result + (locale != null ? locale.hashCode() : 0);
+ return result;
+ }
+ }
+
+ static class GetDefaultMessageReturnArg {
+ String message;
+ boolean foundInBundle;
+
+ public GetDefaultMessageReturnArg(String message, boolean foundInBundle) {
+ this.message = message;
+ this.foundInBundle = foundInBundle;
+ }
+ }
+
+ private static class EmptyResourceBundle extends ResourceBundle {
+ @Override
+ public Enumeration<String> getKeys() {
+ return null; // dummy
+ }
+
+ @Override
+ protected Object handleGetObject(String key) {
+ return null; // dummy
+ }
+ }
+
+}
Added: struts/struts2/branches/STRUTS_3_X/xwork-core/src/main/java/org/apache/struts2/xwork2/util/MemberAccessValueStack.java
URL: http://svn.apache.org/viewvc/struts/struts2/branches/STRUTS_3_X/xwork-core/src/main/java/org/apache/struts2/xwork2/util/MemberAccessValueStack.java?rev=1209569&view=auto
==============================================================================
--- struts/struts2/branches/STRUTS_3_X/xwork-core/src/main/java/org/apache/struts2/xwork2/util/MemberAccessValueStack.java (added)
+++ struts/struts2/branches/STRUTS_3_X/xwork-core/src/main/java/org/apache/struts2/xwork2/util/MemberAccessValueStack.java Fri Dec 2 16:33:03 2011
@@ -0,0 +1,14 @@
+package org.apache.struts2.xwork2.util;
+
+import java.util.Set;
+import java.util.regex.Pattern;
+
+/**
+ * ValueStacks implementing this interface provide a way to remove block or allow access
+ * to properties using regular expressions
+ */
+public interface MemberAccessValueStack {
+ void setExcludeProperties(Set<Pattern> excludeProperties);
+
+ void setAcceptProperties(Set<Pattern> acceptedProperties);
+}
Added: struts/struts2/branches/STRUTS_3_X/xwork-core/src/main/java/org/apache/struts2/xwork2/util/NamedVariablePatternMatcher.java
URL: http://svn.apache.org/viewvc/struts/struts2/branches/STRUTS_3_X/xwork-core/src/main/java/org/apache/struts2/xwork2/util/NamedVariablePatternMatcher.java?rev=1209569&view=auto
==============================================================================
--- struts/struts2/branches/STRUTS_3_X/xwork-core/src/main/java/org/apache/struts2/xwork2/util/NamedVariablePatternMatcher.java (added)
+++ struts/struts2/branches/STRUTS_3_X/xwork-core/src/main/java/org/apache/struts2/xwork2/util/NamedVariablePatternMatcher.java Fri Dec 2 16:33:03 2011
@@ -0,0 +1,143 @@
+/*
+ * Copyright 2002-2006,2009 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.struts2.xwork2.util;
+
+import java.util.ArrayList;
+import java.util.List;
+import java.util.Map;
+import java.util.regex.Matcher;
+import java.util.regex.Pattern;
+
+/**
+ * An implementation of a pattern matcher that uses simple named wildcards. The named wildcards are defined using the
+ * <code>{VARIABLE_NAME}</code> syntax and will match any characters that aren't '/'. Internally, the pattern is
+ * converted into a regular expression where the named wildcard will be translated into <code>([^/]+)</code> so that
+ * at least one character must match in order for the wildcard to be matched successfully. Matched values will be
+ * available in the variable map, indexed by the name they were given in the pattern.
+ *
+ * <p>For example, the following patterns will be processed as so:
+ * </p>
+ * <table>
+ * <tr>
+ * <th>Pattern</th>
+ * <th>Example</th>
+ * <th>Variable Map Contents</th>
+ * </tr>
+ * <tr>
+ * <td><code>/animals/{animal}</code</td>
+ * <td><code>/animals/dog</code></td>
+ * <td>{animal -> dog}</td>
+ * </tr>
+ * <tr>
+ * <td><code>/animals/{animal}/tag/No{id}</code</td>
+ * <td><code>/animals/dog/tag/No23</code></td>
+ * <td>{animal -> dog, id -> 23}</td>
+ * </tr>
+ * <tr>
+ * <td><code>/{language}</code</td>
+ * <td><code>/en</code></td>
+ * <td>{language -> en}</td>
+ * </tr>
+ * </table>
+ *
+ * <p>
+ * Excaping hasn't been implemented since the intended use of these patterns will be in matching URLs.
+ * </p>
+ *
+ * @Since 2.1
+ */
+public class NamedVariablePatternMatcher implements PatternMatcher<NamedVariablePatternMatcher.CompiledPattern> {
+
+ public boolean isLiteral(String pattern) {
+ return (pattern == null || pattern.indexOf('{') == -1);
+ }
+
+ /**
+ * Compiles the pattern.
+ *
+ * @param data The pattern, must not be null or empty
+ * @return The compiled pattern, null if the pattern was null or empty
+ */
+ public CompiledPattern compilePattern(String data) {
+ StringBuilder regex = new StringBuilder();
+ if (data != null && data.length() > 0) {
+ List<String> varNames = new ArrayList<String>();
+ StringBuilder varName = null;
+ for (int x=0; x<data.length(); x++) {
+ char c = data.charAt(x);
+ switch (c) {
+ case '{' : varName = new StringBuilder(); break;
+ case '}' : varNames.add(varName.toString());
+ regex.append("([^/]+)");
+ varName = null;
+ break;
+ default : if (varName == null) {
+ regex.append(c);
+ } else {
+ varName.append(c);
+ }
+ }
+ }
+ return new CompiledPattern(Pattern.compile(regex.toString()), varNames);
+ }
+ return null;
+ }
+
+ /**
+ * Tries to process the data against the compiled expression. If successful, the map will contain
+ * the matched data, using the specified variable names in the original pattern.
+ *
+ * @param map The map of variables
+ * @param data The data to match
+ * @param expr The compiled pattern
+ * @return True if matched, false if not matched, the data was null, or the data was an empty string
+ */
+ public boolean match(Map<String, String> map, String data, CompiledPattern expr) {
+
+ if (data != null && data.length() > 0) {
+ Matcher matcher = expr.getPattern().matcher(data);
+ if (matcher.matches()) {
+ for (int x=0; x<expr.getVariableNames().size(); x++) {
+ map.put(expr.getVariableNames().get(x), matcher.group(x+1));
+ }
+ return true;
+ }
+ }
+ return false;
+ }
+
+ /**
+ * Stores the compiled pattern and the variable names matches will correspond to.
+ */
+ public static class CompiledPattern {
+ private Pattern pattern;
+ private List<String> variableNames;
+
+
+ public CompiledPattern(Pattern pattern, List<String> variableNames) {
+ this.pattern = pattern;
+ this.variableNames = variableNames;
+ }
+
+ public Pattern getPattern() {
+ return pattern;
+ }
+
+ public List<String> getVariableNames() {
+ return variableNames;
+ }
+ }
+}
Added: struts/struts2/branches/STRUTS_3_X/xwork-core/src/main/java/org/apache/struts2/xwork2/util/PatternMatcher.java
URL: http://svn.apache.org/viewvc/struts/struts2/branches/STRUTS_3_X/xwork-core/src/main/java/org/apache/struts2/xwork2/util/PatternMatcher.java?rev=1209569&view=auto
==============================================================================
--- struts/struts2/branches/STRUTS_3_X/xwork-core/src/main/java/org/apache/struts2/xwork2/util/PatternMatcher.java (added)
+++ struts/struts2/branches/STRUTS_3_X/xwork-core/src/main/java/org/apache/struts2/xwork2/util/PatternMatcher.java Fri Dec 2 16:33:03 2011
@@ -0,0 +1,57 @@
+/*
+ * $Id: PatternMatcher.java 1209415 2011-12-02 11:24:48Z lukaszlenart $
+ *
+ * Copyright 2003-2004 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.struts2.xwork2.util;
+
+import java.util.Map;
+
+/**
+ * Compiles and matches a pattern against a value
+ *
+ * @since 2.1
+ */
+public interface PatternMatcher<E extends Object> {
+
+ /**
+ * Determines if the pattern is a simple literal string or contains wildcards that will need to be processed
+ * @param pattern The string pattern
+ * @return True if the pattern doesn't contain processing elements, false otherwise
+ */
+ boolean isLiteral(String pattern);
+
+ /**
+ * <p> Translate the given <code>String</code> into an object
+ * representing the pattern matchable by this class.
+ *
+ * @param data The string to translate.
+ * @return The encoded string
+ * @throws NullPointerException If data is null.
+ */
+ E compilePattern(String data);
+
+ /**
+ * Match a pattern against a string
+ *
+ * @param map The map to store matched values
+ * @param data The string to match
+ * @param expr The compiled wildcard expression
+ * @return True if a match
+ * @throws NullPointerException If any parameters are null
+ */
+ boolean match(Map<String,String> map, String data, E expr);
+
+}
\ No newline at end of file
Added: struts/struts2/branches/STRUTS_3_X/xwork-core/src/main/java/org/apache/struts2/xwork2/util/PropertiesReader.java
URL: http://svn.apache.org/viewvc/struts/struts2/branches/STRUTS_3_X/xwork-core/src/main/java/org/apache/struts2/xwork2/util/PropertiesReader.java?rev=1209569&view=auto
==============================================================================
--- struts/struts2/branches/STRUTS_3_X/xwork-core/src/main/java/org/apache/struts2/xwork2/util/PropertiesReader.java (added)
+++ struts/struts2/branches/STRUTS_3_X/xwork-core/src/main/java/org/apache/struts2/xwork2/util/PropertiesReader.java Fri Dec 2 16:33:03 2011
@@ -0,0 +1,599 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements. See the NOTICE file distributed with
+ * this work for additional information regarding copyright ownership.
+ * The ASF licenses this file to You 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.struts2.xwork2.util;
+
+import java.io.*;
+import java.util.ArrayList;
+import java.util.List;
+
+/**
+ * This class is used to read properties lines. These lines do
+ * not terminate with new-line chars but rather when there is no
+ * backslash sign a the end of the line. This is used to
+ * concatenate multiple lines for readability.
+ *
+ * This class was pulled out of Jakarta Commons Configuration and
+ * Jakarta Commons Lang trunk revision 476093
+ */
+public class PropertiesReader extends LineNumberReader
+{
+ /** Stores the comment lines for the currently processed property.*/
+ private List<String> commentLines;
+
+ /** Stores the name of the last read property.*/
+ private String propertyName;
+
+ /** Stores the value of the last read property.*/
+ private String propertyValue;
+
+ /** Stores the list delimiter character.*/
+ private char delimiter;
+
+ /** Constant for the supported comment characters.*/
+ static final String COMMENT_CHARS = "#!";
+
+ /** Constant for the radix of hex numbers.*/
+ private static final int HEX_RADIX = 16;
+
+ /** Constant for the length of a unicode literal.*/
+ private static final int UNICODE_LEN = 4;
+
+ /** The list of possible key/value separators */
+ private static final char[] SEPARATORS = new char[] {'=', ':'};
+
+ /** The white space characters used as key/value separators. */
+ private static final char[] WHITE_SPACE = new char[]{' ', '\t', '\f'};
+
+ /**
+ * Constructor.
+ *
+ * @param reader A Reader.
+ */
+ public PropertiesReader(Reader reader)
+ {
+ this(reader, ',');
+ }
+
+ /**
+ * Creates a new instance of <code>PropertiesReader</code> and sets
+ * the underlaying reader and the list delimiter.
+ *
+ * @param reader the reader
+ * @param listDelimiter the list delimiter character
+ * @since 1.3
+ */
+ public PropertiesReader(Reader reader, char listDelimiter)
+ {
+ super(reader);
+ commentLines = new ArrayList<String>();
+ delimiter = listDelimiter;
+ }
+
+ /**
+ * Tests whether a line is a comment, i.e. whether it starts with a comment
+ * character.
+ *
+ * @param line the line
+ * @return a flag if this is a comment line
+ * @since 1.3
+ */
+ boolean isCommentLine(String line)
+ {
+ String s = line.trim();
+ // blanc lines are also treated as comment lines
+ return s.length() < 1 || COMMENT_CHARS.indexOf(s.charAt(0)) >= 0;
+ }
+
+ /**
+ * Reads a property line. Returns null if Stream is
+ * at EOF. Concatenates lines ending with "\".
+ * Skips lines beginning with "#" or "!" and empty lines.
+ * The return value is a property definition (<code><name></code>
+ * = <code><value></code>)
+ *
+ * @return A string containing a property value or null
+ *
+ * @throws IOException in case of an I/O error
+ */
+ public String readProperty() throws IOException
+ {
+ commentLines.clear();
+ StringBuilder buffer = new StringBuilder();
+
+ while (true)
+ {
+ String line = readLine();
+ if (line == null)
+ {
+ // EOF
+ return null;
+ }
+
+ if (isCommentLine(line))
+ {
+ commentLines.add(line);
+ continue;
+ }
+
+ line = line.trim();
+
+ if (checkCombineLines(line))
+ {
+ line = line.substring(0, line.length() - 1);
+ buffer.append(line);
+ }
+ else
+ {
+ buffer.append(line);
+ break;
+ }
+ }
+ return buffer.toString();
+ }
+
+ /**
+ * Parses the next property from the input stream and stores the found
+ * name and value in internal fields. These fields can be obtained using
+ * the provided getter methods. The return value indicates whether EOF
+ * was reached (<b>false</b>) or whether further properties are
+ * available (<b>true</b>).
+ *
+ * @return a flag if further properties are available
+ * @throws IOException if an error occurs
+ * @since 1.3
+ */
+ public boolean nextProperty() throws IOException
+ {
+ String line = readProperty();
+
+ if (line == null)
+ {
+ return false; // EOF
+ }
+
+ // parse the line
+ String[] property = parseProperty(line);
+ propertyName = unescapeJava(property[0]);
+ propertyValue = unescapeJava(property[1], delimiter);
+ return true;
+ }
+
+ /**
+ * Returns the comment lines that have been read for the last property.
+ *
+ * @return the comment lines for the last property returned by
+ * <code>readProperty()</code>
+ * @since 1.3
+ */
+ public List<String> getCommentLines()
+ {
+ return commentLines;
+ }
+
+ /**
+ * Returns the name of the last read property. This method can be called
+ * after <code>{@link #nextProperty()}</code> was invoked and its
+ * return value was <b>true</b>.
+ *
+ * @return the name of the last read property
+ * @since 1.3
+ */
+ public String getPropertyName()
+ {
+ return propertyName;
+ }
+
+ /**
+ * Returns the value of the last read property. This method can be
+ * called after <code>{@link #nextProperty()}</code> was invoked and
+ * its return value was <b>true</b>.
+ *
+ * @return the value of the last read property
+ * @since 1.3
+ */
+ public String getPropertyValue()
+ {
+ return propertyValue;
+ }
+
+ /**
+ * Checks if the passed in line should be combined with the following.
+ * This is true, if the line ends with an odd number of backslashes.
+ *
+ * @param line the line
+ * @return a flag if the lines should be combined
+ */
+ private boolean checkCombineLines(String line)
+ {
+ int bsCount = 0;
+ for (int idx = line.length() - 1; idx >= 0 && line.charAt(idx) == '\\'; idx--)
+ {
+ bsCount++;
+ }
+
+ return bsCount % 2 == 1;
+ }
+
+ /**
+ * Parse a property line and return the key and the value in an array.
+ *
+ * @param line the line to parse
+ * @return an array with the property's key and value
+ * @since 1.2
+ */
+ private String[] parseProperty(String line)
+ {
+ // sorry for this spaghetti code, please replace it as soon as
+ // possible with a regexp when the Java 1.3 requirement is dropped
+
+ String[] result = new String[2];
+ StringBuilder key = new StringBuilder();
+ StringBuilder value = new StringBuilder();
+
+ // state of the automaton:
+ // 0: key parsing
+ // 1: antislash found while parsing the key
+ // 2: separator crossing
+ // 3: value parsing
+ int state = 0;
+
+ for (int pos = 0; pos < line.length(); pos++)
+ {
+ char c = line.charAt(pos);
+
+ switch (state)
+ {
+ case 0:
+ if (c == '\\')
+ {
+ state = 1;
+ }
+ else if (contains(WHITE_SPACE, c))
+ {
+ // switch to the separator crossing state
+ state = 2;
+ }
+ else if (contains(SEPARATORS, c))
+ {
+ // switch to the value parsing state
+ state = 3;
+ }
+ else
+ {
+ key.append(c);
+ }
+
+ break;
+
+ case 1:
+ if (contains(SEPARATORS, c) || contains(WHITE_SPACE, c))
+ {
+ // this is an escaped separator or white space
+ key.append(c);
+ }
+ else
+ {
+ // another escaped character, the '\' is preserved
+ key.append('\\');
+ key.append(c);
+ }
+
+ // return to the key parsing state
+ state = 0;
+
+ break;
+
+ case 2:
+ if (contains(WHITE_SPACE, c))
+ {
+ // do nothing, eat all white spaces
+ state = 2;
+ }
+ else if (contains(SEPARATORS, c))
+ {
+ // switch to the value parsing state
+ state = 3;
+ }
+ else
+ {
+ // any other character indicates we encoutered the beginning of the value
+ value.append(c);
+
+ // switch to the value parsing state
+ state = 3;
+ }
+
+ break;
+
+ case 3:
+ value.append(c);
+ break;
+ }
+ }
+
+ result[0] = key.toString().trim();
+ result[1] = value.toString().trim();
+
+ return result;
+ }
+
+ /**
+ * <p>Unescapes any Java literals found in the <code>String</code> to a
+ * <code>Writer</code>.</p> This is a slightly modified version of the
+ * StringEscapeUtils.unescapeJava() function in commons-lang that doesn't
+ * drop escaped separators (i.e '\,').
+ *
+ * @param str the <code>String</code> to unescape, may be null
+ * @param delimiter the delimiter for multi-valued properties
+ * @return the processed string
+ * @throws IllegalArgumentException if the Writer is <code>null</code>
+ */
+ protected static String unescapeJava(String str, char delimiter)
+ {
+ if (str == null)
+ {
+ return null;
+ }
+ int sz = str.length();
+ StringBuilder out = new StringBuilder(sz);
+ StringBuffer unicode = new StringBuffer(UNICODE_LEN);
+ boolean hadSlash = false;
+ boolean inUnicode = false;
+ for (int i = 0; i < sz; i++)
+ {
+ char ch = str.charAt(i);
+ if (inUnicode)
+ {
+ // if in unicode, then we're reading unicode
+ // values in somehow
+ unicode.append(ch);
+ if (unicode.length() == UNICODE_LEN)
+ {
+ // unicode now contains the four hex digits
+ // which represents our unicode character
+ try
+ {
+ int value = Integer.parseInt(unicode.toString(), HEX_RADIX);
+ out.append((char) value);
+ unicode.setLength(0);
+ inUnicode = false;
+ hadSlash = false;
+ }
+ catch (NumberFormatException nfe)
+ {
+ throw new RuntimeException("Unable to parse unicode value: " + unicode, nfe);
+ }
+ }
+ continue;
+ }
+
+ if (hadSlash)
+ {
+ // handle an escaped value
+ hadSlash = false;
+
+ if (ch == '\\')
+ {
+ out.append('\\');
+ }
+ else if (ch == '\'')
+ {
+ out.append('\'');
+ }
+ else if (ch == '\"')
+ {
+ out.append('"');
+ }
+ else if (ch == 'r')
+ {
+ out.append('\r');
+ }
+ else if (ch == 'f')
+ {
+ out.append('\f');
+ }
+ else if (ch == 't')
+ {
+ out.append('\t');
+ }
+ else if (ch == 'n')
+ {
+ out.append('\n');
+ }
+ else if (ch == 'b')
+ {
+ out.append('\b');
+ }
+ else if (ch == delimiter)
+ {
+ out.append('\\');
+ out.append(delimiter);
+ }
+ else if (ch == 'u')
+ {
+ // uh-oh, we're in unicode country....
+ inUnicode = true;
+ }
+ else
+ {
+ out.append(ch);
+ }
+
+ continue;
+ }
+ else if (ch == '\\')
+ {
+ hadSlash = true;
+ continue;
+ }
+ out.append(ch);
+ }
+
+ if (hadSlash)
+ {
+ // then we're in the weird case of a \ at the end of the
+ // string, let's output it anyway.
+ out.append('\\');
+ }
+
+ return out.toString();
+ }
+
+ /**
+ * <p>Checks if the object is in the given array.</p>
+ *
+ * <p>The method returns <code>false</code> if a <code>null</code> array is passed in.</p>
+ *
+ * @param array the array to search through
+ * @param objectToFind the object to find
+ * @return <code>true</code> if the array contains the object
+ */
+ public boolean contains(char[] array, char objectToFind) {
+ if (array == null) {
+ return false;
+ }
+ for (char anArray : array) {
+ if (objectToFind == anArray) {
+ return true;
+ }
+ }
+ return false;
+ }
+
+ /**
+ * <p>Unescapes any Java literals found in the <code>String</code>.
+ * For example, it will turn a sequence of <code>'\'</code> and
+ * <code>'n'</code> into a newline character, unless the <code>'\'</code>
+ * is preceded by another <code>'\'</code>.</p>
+ *
+ * @param str the <code>String</code> to unescape, may be null
+ * @return a new unescaped <code>String</code>, <code>null</code> if null string input
+ */
+ public static String unescapeJava(String str) {
+ if (str == null) {
+ return null;
+ }
+ try {
+ StringWriter writer = new StringWriter(str.length());
+ unescapeJava(writer, str);
+ return writer.toString();
+ } catch (IOException ioe) {
+ // this should never ever happen while writing to a StringWriter
+ ioe.printStackTrace();
+ return null;
+ }
+ }
+
+ /**
+ * <p>Unescapes any Java literals found in the <code>String</code> to a
+ * <code>Writer</code>.</p>
+ *
+ * <p>For example, it will turn a sequence of <code>'\'</code> and
+ * <code>'n'</code> into a newline character, unless the <code>'\'</code>
+ * is preceded by another <code>'\'</code>.</p>
+ *
+ * <p>A <code>null</code> string input has no effect.</p>
+ *
+ * @param out the <code>Writer</code> used to output unescaped characters
+ * @param str the <code>String</code> to unescape, may be null
+ * @throws IllegalArgumentException if the Writer is <code>null</code>
+ * @throws IOException if error occurs on underlying Writer
+ */
+ public static void unescapeJava(Writer out, String str) throws IOException {
+ if (out == null) {
+ throw new IllegalArgumentException("The Writer must not be null");
+ }
+ if (str == null) {
+ return;
+ }
+ int sz = str.length();
+ StringBuffer unicode = new StringBuffer(4);
+ boolean hadSlash = false;
+ boolean inUnicode = false;
+ for (int i = 0; i < sz; i++) {
+ char ch = str.charAt(i);
+ if (inUnicode) {
+ // if in unicode, then we're reading unicode
+ // values in somehow
+ unicode.append(ch);
+ if (unicode.length() == 4) {
+ // unicode now contains the four hex digits
+ // which represents our unicode character
+ try {
+ int value = Integer.parseInt(unicode.toString(), 16);
+ out.write((char) value);
+ unicode.setLength(0);
+ inUnicode = false;
+ hadSlash = false;
+ } catch (NumberFormatException nfe) {
+ throw new RuntimeException("Unable to parse unicode value: " + unicode, nfe);
+ }
+ }
+ continue;
+ }
+ if (hadSlash) {
+ // handle an escaped value
+ hadSlash = false;
+ switch (ch) {
+ case '\\':
+ out.write('\\');
+ break;
+ case '\'':
+ out.write('\'');
+ break;
+ case '\"':
+ out.write('"');
+ break;
+ case 'r':
+ out.write('\r');
+ break;
+ case 'f':
+ out.write('\f');
+ break;
+ case 't':
+ out.write('\t');
+ break;
+ case 'n':
+ out.write('\n');
+ break;
+ case 'b':
+ out.write('\b');
+ break;
+ case 'u':
+ {
+ // uh-oh, we're in unicode country....
+ inUnicode = true;
+ break;
+ }
+ default :
+ out.write(ch);
+ break;
+ }
+ continue;
+ } else if (ch == '\\') {
+ hadSlash = true;
+ continue;
+ }
+ out.write(ch);
+ }
+ if (hadSlash) {
+ // then we're in the weird case of a \ at the end of the
+ // string, let's output it anyway.
+ out.write('\\');
+ }
+ }
+}