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

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

Go to download

Jakarta Faces defines an MVC framework for building user interfaces for web applications, including UI components, state management, event handing, input validation, page navigation, and support for internationalization and accessibility.

There is a newer version: 4.1.2
Show newest version
/*
 * Copyright (c) 2023 Contributors to Eclipse Foundation.
 * Copyright (c) 1997, 2020 Oracle and/or its affiliates. All rights reserved.
 *
 * This program and the accompanying materials are made available under the
 * terms of the Eclipse Public License v. 2.0, which is available at
 * http://www.eclipse.org/legal/epl-2.0.
 *
 * This Source Code may also be made available under the following Secondary
 * Licenses when the conditions for such availability set forth in the
 * Eclipse Public License v. 2.0 are satisfied: GNU General Public License,
 * version 2 with the GNU Classpath Exception, which is available at
 * https://www.gnu.org/software/classpath/license.html.
 *
 * SPDX-License-Identifier: EPL-2.0 OR GPL-2.0 WITH Classpath-exception-2.0
 */

package com.sun.faces.application;

import static java.nio.charset.StandardCharsets.UTF_8;
import static java.util.logging.Level.FINE;
import static java.util.logging.Level.FINEST;
import static java.util.logging.Level.WARNING;

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

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) { 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) { this.ref = ref; this.replacement = getUtf8InfoBytes(replacement); } /** * Order by the index position of the source UTF8Info reference. */ @Override 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 final 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.getDeclaredConstructor().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 (IllegalArgumentException | ReflectiveOperationException | SecurityException | IOException 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"; try (InputStream in = ConverterPropertyEditorFactory.class.getResourceAsStream(resourceName)) { if (in != null) { 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); } } } /** * 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 = UNDERSCORE_PATTERN.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, 'L' + newClassName + ';'), 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 final ClassLoader targetLoader; // The class loader which loaded the base class private final ClassLoader myLoader; public DisposableClassLoader(ClassLoader targetLoader) { super(targetLoader); this.targetLoader = targetLoader; 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 clazz = findLoadedClass(name); // Otherwise check if myLoader is able to load it ... if (clazz == null && myLoader != null && myLoader != targetLoader) { try { clazz = myLoader.loadClass(name); } catch (ClassNotFoundException ignored) { if (LOGGER.isLoggable(FINEST)) { LOGGER.log(FINEST, "Ignoring ClassNotFoundException, continuing with parent ClassLoader.", ignored); } } } // Otherwise go ahead with the targetLoader and with the dynamic // class generation ... if (clazz == null) { clazz = super.loadClass(name, false); } if (resolve) { resolveClass(clazz); } return clazz; } /** * 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(FINE)) { LOGGER.fine("Defined editorClass " + editorClass); } return editorClass; } // This will just cause ClassNotFoundException to be thrown. return super.findClass(className); } } private static final Pattern UNDERSCORE_PATTERN = 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 final 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 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. classLoaderCache = new WeakHashMap<>(); } DisposableClassLoader loader; WeakReference loaderRef = classLoaderCache.get(targetClass.getClassLoader()); if (loaderRef == null || (loader = loaderRef.get()) == null) { loader = new DisposableClassLoader(targetClass.getClassLoader()); classLoaderCache.put(targetClass.getClassLoader(), new WeakReference<>(loader)); } return (Class) loader.loadClass(className); } catch (ClassNotFoundException e) { if (LOGGER.isLoggable(WARNING)) { LOGGER.log(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 = text.getBytes(UTF_8); 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 - 2025 Weber Informatics LLC | Privacy Policy