All Downloads are FREE. Search and download functionalities are using the official Maven repository.

com.sun.faces.application.ConverterPropertyEditorFactory Maven / Gradle / Ivy

/*
 * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS HEADER.
 *
 * Copyright (c) 1997-2010 Oracle and/or its affiliates. All rights reserved.
 *
 * The contents of this file are subject to the terms of either the GNU
 * General Public License Version 2 only ("GPL") or the Common Development
 * and Distribution License("CDDL") (collectively, the "License").  You
 * may not use this file except in compliance with the License.  You can
 * obtain a copy of the License at
 * https://glassfish.dev.java.net/public/CDDL+GPL_1_1.html
 * or packager/legal/LICENSE.txt.  See the License for the specific
 * language governing permissions and limitations under the License.
 *
 * When distributing the software, include this License Header Notice in each
 * file and include the License file at packager/legal/LICENSE.txt.
 *
 * GPL Classpath Exception:
 * Oracle designates this particular file as subject to the "Classpath"
 * exception as provided by Oracle in the GPL Version 2 section of the License
 * file that accompanied this code.
 *
 * Modifications:
 * If applicable, add the following below the License Header, with the fields
 * enclosed by brackets [] replaced by your own identifying information:
 * "Portions Copyright [year] [name of copyright owner]"
 *
 * Contributor(s):
 * If you wish your version of this file to be governed by only the CDDL or
 * only the GPL Version 2, indicate your decision by adding "[Contributor]
 * elects to include this software in this distribution under the [CDDL or GPL
 * Version 2] license."  If you don't indicate a single choice of license, a
 * recipient has the option to distribute your version of this file under
 * either the CDDL, the GPL Version 2 or to extend the choice of license to
 * its licensees as provided above.  However, if you add GPL Version 2 code
 * and therefore, elected the GPL Version 2 license, then the option applies
 * only if the new code is made subject to such option by the copyright
 * holder.
 */

package com.sun.faces.application;

import java.io.ByteArrayOutputStream;
import java.io.IOException;
import java.io.InputStream;
import java.io.UnsupportedEncodingException;
import java.lang.ref.WeakReference;
import java.util.Map;
import java.util.TreeSet;
import java.util.WeakHashMap;
import java.util.HashMap;
import java.util.logging.Level;
import java.util.logging.Logger;
import java.util.regex.Matcher;
import java.util.regex.Pattern;
import java.security.AccessController;
import java.security.PrivilegedAction;

import com.sun.faces.util.FacesLogger;

/**
 * 

* Factory for dynamically generating PropertyEditor classes that extend * {@link ConverterPropertyEditorBase} and replace any references to the target * class from the template with a supplied target class. *

*/ public class ConverterPropertyEditorFactory { private static final Logger LOGGER = FacesLogger.APPLICATION.getLogger(); /** *

* Capture information extracted from a "template" PropertyEditor class, and * perform manipulation of the byte codes in order to generate the bytes for * a new PropertyEditor class. *

*

* The new class bytes are generated by identifying UTF8Info entries in the * constant pool of the template class, and replacing them with new UTF8 * constants to define a new class. The constants to be replaced are those * for: *

    *
  • The name of the class itself * (com/sun/faces/application/ConverterPropertyEditorFor_XXXX).
  • *
  • The class name as a type reference * (Lcom/sun/faces/application/ConverterPropertyEditorFor_XXXX;).
  • *
  • The name of the target class that the editor will be * manipulating (java/util/Date in the current template).
  • *
*

*/ private static class ClassTemplateInfo { /** * Capture details of the location of a UTF8Info entry in the constant * pool of the template class. */ private static class Utf8InfoRef { /** * The position of the constant in the byte array that defines the * template class. */ int index; /** * The number of bytes that the constant occupies in the byte array * that defines the template class. */ int length; public Utf8InfoRef(int index, int length) { super(); this.index = index; this.length = length; } } /** * Capture details of a single substitution to be made in the template * class while generating the new class. Implements * {@link java.lang.Comparable} so that the replacements can be ordered * according to the order they appear in the source. */ private static class Utf8InfoReplacement implements Comparable { /** * The utf8 constant reference from the template source. */ Utf8InfoRef ref; /** * The bytes to replace the constant with (must also be a valid utf8 * constant pool entry). */ byte[] replacement; public Utf8InfoReplacement(Utf8InfoRef ref, String replacement) { super(); this.ref = ref; this.replacement = getUtf8InfoBytes(replacement); } /** * Order by the index position of the source UTF8Info reference. */ public int compareTo(Utf8InfoReplacement rhs) { return ref.index - rhs.ref.index; } } // The source template class on which to base the definition of the new // PropertyEditor classes. private Class templateClass; // The bytes that define the source template class. private byte[] templateBytes; // The constant_pool_count from the template class bytecodes. private int constant_pool_count; // Reference to the class name utf8 constant private Utf8InfoRef classNameConstant; // Reference to the class name ref utf8 constant private Utf8InfoRef classNameRefConstant; // Reference to the target class name utf8 constant private Utf8InfoRef targetClassConstant; /** * Default constructor uses the {@link ConverterPropertyEditorFor_XXXX} * class as the source template. */ public ClassTemplateInfo() { this(ConverterPropertyEditorFor_XXXX.class); } /** * Construct a template info instance based on the supplied class. * * @param templateClass * is a "template" class (but not in the java generics sense) * which must extend {@link ConverterPropertyEditorBase} and * override the * {@link ConverterPropertyEditorBase#getTargetClass} method. */ public ClassTemplateInfo( Class templateClass) { this.templateClass = templateClass; try { ConverterPropertyEditorBase tc = templateClass.newInstance(); Class templateTargetClass = tc.getTargetClass(); loadTemplateBytes(); classNameConstant = findConstant(getVMClassName(templateClass)); classNameRefConstant = findConstant( new StringBuilder(64).append('L').append(getVMClassName(templateClass)).append(';').toString()); targetClassConstant = findConstant(getVMClassName(templateTargetClass)); } catch (Exception e) { if (LOGGER.isLoggable(Level.FINE)) { LOGGER.log(Level.FINE, "Unexected exception ClassTemplateInfo", e); } } } /** * Check whether the targetBytes match the content of the * templateBytes at the given index. * * @param targetBytes * byte array to compare. * @param index * the index into templateBytes at which to * compare. * @return true if the bytes from targetBytes match the * bytes from templateBytes. */ private boolean matchAtIndex(byte[] targetBytes, int index) { if (index < 0 || index + targetBytes.length > templateBytes.length) { return false; } for (int i = 0; i < targetBytes.length; ++i) { if (targetBytes[i] != templateBytes[index + i]) { return false; } } return true; } /** * Find an instance of UTF8Info in the source class's constant pool * where the text matches the given argument. * * @param text * the text that the UTF8Info must contain. * @return A {@link Utf8InfoRef} instance refering to the matched * constant pool entry, or null if no match was * found. */ private Utf8InfoRef findConstant(String text) { byte[] utf8InfoBytes = getUtf8InfoBytes(text); assert utf8InfoBytes[0] == 1; int off = 10; for (int i = 1; i < constant_pool_count && off < templateBytes.length; ++i) { if (matchAtIndex(utf8InfoBytes, off)) { return new Utf8InfoRef(off, utf8InfoBytes.length); } switch (templateBytes[off]) { case 1:// CONSTANT_Utf8 { int len = (templateBytes[off + 1] & 0xff << 8) + (templateBytes[off + 2] & 0xff); off += 3 + len; break; } case 7:// CONSTANT_Class case 8:// CONSTANT_String off += 3; break; case 3:// CONSTANT_Integer case 4:// CONSTANT_Float case 9:// CONSTANT_Fieldref case 10:// CONSTANT_Methodref case 11:// CONSTANT_InterfaceMethodref case 12:// CONSTANT_NameAndType off += 5; break; case 5:// CONSTANT_Long case 6:// CONSTANT_Double off += 9; break; default: throw new IllegalArgumentException( "Unrecognized class file constant pool tag " + templateBytes[off]); } } return null; } /** * Obtain the bytes that define the given class by looking for the * ".class" resource and loading the binary data. * * @throws IOException if an error occurs loading the binary data */ private void loadTemplateBytes() throws IOException { String resourceName = '/' + templateClass.getName().replace('.', '/') + ".class"; InputStream in = ConverterPropertyEditorFactory.class .getResourceAsStream(resourceName); if (in != null) { try { ByteArrayOutputStream baos = new ByteArrayOutputStream(); byte[] buff = new byte[1024]; int more; while ((more = in.read(buff)) > 0) { baos.write(buff, 0, more); } templateBytes = baos.toByteArray(); // The bytes should start with the CAFEBABE "magic" header // for class files. assert templateBytes.length > 9; assert templateBytes[0] == (byte) 0xCA; assert templateBytes[1] == (byte) 0xFE; assert templateBytes[2] == (byte) 0xBA; assert templateBytes[3] == (byte) 0xBE; constant_pool_count = ((templateBytes[8] & 0xff) << 8) + (templateBytes[9] & 0xff); } finally { in.close(); } } } /** * Generate a class name to use for the generated PropertyEditor class, * based on the full name of the target class. This is done by replacing * the "XXXX" in the template class name with a version of the target * class name. * * @param targetClass * The target class which the PropertyEditor will operate on. * @param vmFormat * If true, the package name components will be '/' * separated. Otherwise they will be '.' separated. * @return The full name to use for the generated PropertyEditor class. */ public String generateClassNameFor(Class targetClass, boolean vmFormat) { String name = targetClass.getName(); if (targetClass.isArray()) { int idx = name.lastIndexOf('['); int bracketCount = idx + 1; int semiIdx = name.indexOf(';'); if (semiIdx == -1) { // primitive array name = PRIM_MAP.get(name.charAt(idx + 1)); } else { // Object array name = name.substring(idx + 2, semiIdx); } name += "Array" + bracketCount + 'd'; } Matcher m = UnderscorePattern.matcher(name); // Replace existing underscores with one extra underscore. name = m.replaceAll("$0_"); // Replace existing dots with a single underscore. name = name.replace('.', '_'); if (vmFormat) { return getVMClassName(templateClass).replace("XXXX", name); } else { return templateClass.getName().replace("XXXX", name); } } /** * Extract the original target class name from the generated * PropertyEditor class name. (This is the reverse of * {@link #generateClassNameFor}). * * @param className * name of the generated PropertyEditor class. * @return the target class name, or null if the given * className was not a generated PropertyEditor * name. */ public String getTargetClassName(String className) { String prefix = templateClass.getName().replace("XXXX", ""); if (className.startsWith(prefix)) { String name = className.substring(prefix.length()); name = SingleUnderscorePattern.matcher(name) .replaceAll("$1.$2"); name = MultipleUnderscorePattern.matcher(name).replaceAll("$1"); return name; } return null; } /** * Generate the bytes for a new class based on the * templateBytes, but with all the replacements in * replacements performed. * * @param replacements one or more Utf8InfoReplacments * @return the bytes for the new class definition. */ private byte[] replaceInTemplate(Utf8InfoReplacement... replacements) { ByteArrayOutputStream baos = new ByteArrayOutputStream(); // Sort the replacements, and weed out any that have no source match TreeSet sorted = new TreeSet(); for (Utf8InfoReplacement r : replacements) { if (r.ref != null && r.replacement != null) { sorted.add(r); } } // Now create the output bytes by applying the remaining // replacements int from = 0; for (Utf8InfoReplacement r : sorted) { baos.write(templateBytes, from, r.ref.index - from); from = r.ref.index + r.ref.length; baos.write(r.replacement, 0, r.replacement.length); } baos.write(templateBytes, from, templateBytes.length - from); return baos.toByteArray(); } /** * @return the bytes for a new class with the given name and target * class. * * @param newClassName * the binary name of the new class. * @param targetClassName * the binary name of the PropertyEditor's target class. */ public byte[] generateClassBytesFor(String newClassName, String targetClassName) { return replaceInTemplate(new Utf8InfoReplacement(classNameConstant, newClassName), new Utf8InfoReplacement(classNameRefConstant, new StringBuilder(32).append('L').append(newClassName).append(';').toString()), new Utf8InfoReplacement( targetClassConstant, targetClassName)); } } /** *

* A custom class loader for the definition of the generated classes. When * the generated class is loaded, it will need to be able to resolve both * the base class ({@link ConverterPropertyEditorBase}) which comes from * myLoader and the target class which comes from * targetLoader. This class loader defines only the * generated class, and delegates to the above two loaders for the rest. *

*

* The {@link ConverterPropertyEditorFactory} will keep a cache of these * class loaders (via weak references), one for each class loader that the * target classes come from. That way the target class loader (which is * likely to be a webapp specific loader) can be disposed of and replaced * when the webapp is removed or reinstalled. *

*/ private class DisposableClassLoader extends ClassLoader { // The class loader which loaded the target class. private ClassLoader targetLoader; // The class loader which loaded the base class private ClassLoader myLoader; public DisposableClassLoader(ClassLoader targetLoader) { super(targetLoader); this.targetLoader = targetLoader; this.myLoader = ConverterPropertyEditorBase.class.getClassLoader(); } /** * Override class loading to enable possible delegation to the two class * loaders, rather than just to the parent. */ @Override protected synchronized Class loadClass(String name, boolean resolve) throws ClassNotFoundException { // First, check if the class has already been loaded Class c = findLoadedClass(name); // Otherwise check if myLoader is able to load it ... //noinspection ObjectEquality if ((c == null) && (myLoader != null) && (myLoader != targetLoader)) { try { c = myLoader.loadClass(name); } catch (ClassNotFoundException ignored) { } } // Otherwise go ahead with the targetLoader and with the dynamic // class generation ... if (c == null) { c = super.loadClass(name, false); } if (resolve) { resolveClass(c); } return c; } /** * If super.loadClass is unable to locate a class, it * will call this method to define it. If the className * is a generated PropertyEditor class name, then create the new class. * Otherwise call super.findClass which will throw a * {@link ClassNotFoundException}. */ @Override protected Class findClass(String className) throws ClassNotFoundException { String targetClassName = getTemplateInfo().getTargetClassName( className); if (targetClassName != null) { // Need to generate an appropriate PropertyEditor class for the // specified target class. byte[] classBytes = getTemplateInfo().generateClassBytesFor( className.replace('.', '/'), targetClassName.replace('.', '/')); Class editorClass = defineClass(className, classBytes, 0, classBytes.length); if (LOGGER.isLoggable(Level.FINE)) { LOGGER.fine("Defined editorClass " + editorClass); } return editorClass; } // This will just cause ClassNotFoundException to be thrown. return super.findClass(className); } } private static final Pattern UnderscorePattern = Pattern.compile("_+"); private static final Pattern SingleUnderscorePattern = Pattern .compile("([^_])_([^_])"); private static final Pattern MultipleUnderscorePattern = Pattern .compile("_(_+)"); private static ConverterPropertyEditorFactory defaultInstance; // Template information extracted from the source template class. private ClassTemplateInfo templateInfo; // Cache of DisposableClassLoaders keyed on the class loader of the target. private Map> classLoaderCache; private static final Map PRIM_MAP = new HashMap(8, 1.0f); static { PRIM_MAP.put('B', "byte"); PRIM_MAP.put('C', "char"); PRIM_MAP.put('S', "short"); PRIM_MAP.put('I', "int"); PRIM_MAP.put('F', "float"); PRIM_MAP.put('J', "long"); PRIM_MAP.put('D', "double"); PRIM_MAP.put('Z', "boolean"); } /** * Create a ConverterPropertyEditorFactory that uses the * default template class ({@link ConverterPropertyEditorFor_XXXX}). */ public ConverterPropertyEditorFactory() { // Use the default template class templateInfo = new ClassTemplateInfo(); } /** * Create a ConverterPropertyEditorFactory that uses the * specified template class. * * @param templateClass the template */ public ConverterPropertyEditorFactory( Class templateClass) { templateInfo = new ClassTemplateInfo(templateClass); } /** * @return the single default instance of this class (created with the * default template class). */ public static synchronized ConverterPropertyEditorFactory getDefaultInstance() { if (defaultInstance == null) { defaultInstance = new ConverterPropertyEditorFactory(); } return defaultInstance; } private ClassTemplateInfo getTemplateInfo() { return templateInfo; } /** * Return a PropertyEditor class appropriate for editing the given * targetClass. The new class will be defined from a * {@link DisposableClassLoader}. * * @param targetClass * the class of object that the returned property editor class * will be editing. * @return the dynamically generated PropertyEditor class. */ @SuppressWarnings("unchecked") public Class definePropertyEditorClassFor( final Class targetClass) { try { String className = getTemplateInfo().generateClassNameFor( targetClass, false); if (classLoaderCache == null) { // Use a WeakHashMap so as not to prevent the class loaders from // being garbage collected. //noinspection CollectionWithoutInitialCapacity classLoaderCache = new WeakHashMap>(); } DisposableClassLoader loader; WeakReference loaderRef = classLoaderCache .get(targetClass.getClassLoader()); if (loaderRef == null || (loader = loaderRef.get()) == null) { loader = (DisposableClassLoader) AccessController.doPrivileged( new PrivilegedAction() { public Object run() { return new DisposableClassLoader(targetClass.getClassLoader()); } }); if (loader == null) { return null; } classLoaderCache.put(targetClass.getClassLoader(), new WeakReference(loader)); } return (Class) loader .loadClass(className); } catch (ClassNotFoundException e) { if (LOGGER.isLoggable(Level.WARNING)) { LOGGER.log(Level.WARNING, "definePropertyEditorClassFor: ClassNotFoundException: " + e.getMessage(), e); } } return null; } /** * @param c * the class to find the name of. * @return the binary name of the class as used by the VM ('/' instead of * '.' as a package name separator). */ private static String getVMClassName(Class c) { return c.getName().replace('.', '/'); } /** * Create a UTF8Info constant pool structure for the given text. * * @param text * the text to create the UTF8 constant from. * @return the bytes for the UTF8Info constant pool entry, including the * tag, length, and utf8 content. */ private static byte[] getUtf8InfoBytes(String text) { byte[] utf8; try { utf8 = text.getBytes("UTF-8"); } catch (UnsupportedEncodingException e) { utf8 = text.getBytes(); } byte[] info = new byte[utf8.length + 3]; info[0] = 1; info[1] = (byte) ((utf8.length >> 8) & 0xff); info[2] = (byte) (utf8.length & 0xff); System.arraycopy(utf8, 0, info, 3, utf8.length); return info; } }




© 2015 - 2024 Weber Informatics LLC | Privacy Policy